diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..df53e16 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-15 - psutil process iteration performance +**Learning:** Querying expensive attributes like `memory_info` and `status` for all processes in `psutil.process_iter` incurs significant overhead, especially on Windows. +**Action:** Only query basic attributes like `pid` and `name` initially, and lazily fetch expensive attributes wrapped in try/except blocks only for the specific target processes. diff --git a/desktop_services/excel_service.py b/desktop_services/excel_service.py index d79b36b..ee15500 100644 --- a/desktop_services/excel_service.py +++ b/desktop_services/excel_service.py @@ -206,17 +206,30 @@ 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"]): + # OPTIMIZATION: Only fetch basic attributes initially to avoid expensive calls for all processes + for proc in psutil.process_iter(["pid", "name"]): try: info = proc.info process_name = info.get("name") or "" if process_name.lower() != "excel.exe": continue pid = int(info["pid"]) + + # Lazily fetch expensive attributes only for target processes + try: + memory_info = proc.memory_info() + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, AttributeError): + memory_info = None + + try: + status = proc.status() + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, AttributeError): + 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, } 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..76c8107 100644 --- a/desktop_services/operations_cockpit_service.py +++ b/desktop_services/operations_cockpit_service.py @@ -852,19 +852,32 @@ def _default_processes() -> list[dict[str, Any]]: if psutil is None: return [] processes = [] - for proc in psutil.process_iter(["pid", "name", "memory_info", "status"]): + # OPTIMIZATION: Only fetch basic attributes initially to avoid expensive calls for all processes + for proc in psutil.process_iter(["pid", "name"]): try: info = proc.info except (psutil.NoSuchProcess, psutil.AccessDenied): continue if str(info.get("name") or "").casefold() != "excel.exe": continue + + # Lazily fetch expensive attributes only for target processes + try: + memory_info = proc.memory_info() + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, AttributeError): + memory_info = None + + try: + status = proc.status() + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, AttributeError): + status = "running" + 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 diff --git a/tests/test_excel_desktop_service.py b/tests/test_excel_desktop_service.py index 848bd3f..dad50fd 100644 --- a/tests/test_excel_desktop_service.py +++ b/tests/test_excel_desktop_service.py @@ -352,7 +352,7 @@ def status(self): "is_responsive": True, } ] - assert requested_fields == [["pid", "name", "memory_info", "status"]] + assert requested_fields == [["pid", "name"]] assert title_lookup_pids == [{42}]