diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..c566df5 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2025-05-20 - Optimize psutil.process_iter overhead +**Learning:** Querying all attributes (like `memory_info` and `status`) in `psutil.process_iter` for every process on the system is highly inefficient, as it fetches expensive data for many processes we don't care about. +**Action:** Only query `['pid', 'name']` initially. Lazily fetch expensive attributes like `memory_info()` and `status()` on the individual process objects only after filtering for our target processes (e.g., 'excel.exe'). Wrap these in try-except to safely catch process termination exceptions like `psutil.NoSuchProcess`, `psutil.AccessDenied`, and `psutil.ZombieProcess`. diff --git a/desktop_services/excel_service.py b/desktop_services/excel_service.py index d79b36b..16c5e63 100644 --- a/desktop_services/excel_service.py +++ b/desktop_services/excel_service.py @@ -206,17 +206,27 @@ def get_running_instances(self) -> List[Dict[str, Any]]: def _get_excel_process_snapshots(self) -> Dict[int, Dict[str, Any]]: """Find Excel processes with one psutil scan for refresh performance.""" snapshots: Dict[int, Dict[str, Any]] = {} - for proc in psutil.process_iter(["pid", "name", "memory_info", "status"]): + for proc in psutil.process_iter(["pid", "name"]): try: info = proc.info process_name = info.get("name") or "" - if process_name.lower() != "excel.exe": + if process_name.casefold() != "excel.exe": continue pid = int(info["pid"]) + + # Lazily fetch expensive attributes only for target processes to avoid + # querying them for all system processes during the psutil iteration. + try: + memory_info = proc.memory_info() + status = proc.status() + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + memory_info = None + status = "running" + snapshots[pid] = { "name": process_name, - "memory_mb": self._memory_mb_from_info(info.get("memory_info")), - "status": info.get("status") or "running", + "memory_mb": self._memory_mb_from_info(memory_info), + "status": status or "running", } except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): continue diff --git a/desktop_services/operations_cockpit_service.py b/desktop_services/operations_cockpit_service.py index be2853d..f968732 100644 --- a/desktop_services/operations_cockpit_service.py +++ b/desktop_services/operations_cockpit_service.py @@ -852,19 +852,28 @@ def _default_processes() -> list[dict[str, Any]]: if psutil is None: return [] processes = [] - for proc in psutil.process_iter(["pid", "name", "memory_info", "status"]): + for proc in psutil.process_iter(["pid", "name"]): try: info = proc.info - except (psutil.NoSuchProcess, psutil.AccessDenied): + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): continue if str(info.get("name") or "").casefold() != "excel.exe": continue + + # Lazily fetch expensive attributes only for target processes to avoid + # querying them for all system processes during the psutil iteration. + try: + memory_info = proc.memory_info() + status = proc.status() + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + memory_info = None + status = None processes.append( { "pid": info.get("pid"), "name": info.get("name"), - "memory_info": info.get("memory_info"), - "status": info.get("status"), + "memory_info": memory_info, + "status": status, } ) return processes