fix: support macOS system health metrics#4616
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds cross-platform support for the system health (CPU/RAM) metrics by keeping the existing Linux /proc collectors and introducing a psutil fallback path for platforms where procfs is unavailable (e.g., macOS). It also updates regression tests to cover the “no /proc” behavior.
Changes:
- Add a procfs-first,
psutil-fallback implementation for aggregate CPU and memory metrics inapi/system_health.py. - Add a regression test that simulates missing procfs and verifies the
psutilfallback behavior. - Add
psutilto the minimal server dependencies inrequirements.txt.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
api/system_health.py |
Adds psutil fallback for CPU/RAM metrics when procfs access fails/unavailable. |
tests/test_issue693_system_health_panel.py |
Adds a regression test for the macOS/no-procfs fallback path; updates safety assertions accordingly. |
requirements.txt |
Adds psutil to minimal dependencies with explanatory comments. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import shutil | ||
| import time | ||
| from importlib import import_module | ||
| from datetime import datetime, timezone |
There was a problem hiding this comment.
Fixed in 8aa5ea60: the docstring now documents the procfs-first + psutil fallback behavior, while preserving the aggregate/no-private-process-data contract.
|
Updated in
Validation:
|
|
Pulled the branch ( The fallback logic is soundThe OSError gate cleanly separates "this platform has no procfs" from "procfs is present but malformed." The dependency declaration is the one thing to weighThe PR adds But psutil is only ever reached on the non-Linux fallback path, and the module already degrades gracefully when it's absent. except Exception as exc:
errors.append(_safe_error(name, exc))which emits a safe That matters because the repo has a documented stance against exactly this. The comment block right above your change in
and RecommendationTwo coherent options, maintainer's call:
I'd lean option 1 since the code already proves the optional path works, but it's a project-philosophy choice rather than a correctness one. Everything else (tests at |
nesquena-hermes
left a comment
There was a problem hiding this comment.
Thanks for the PR @jkobject — the macOS procfs fallback is a real gap, and the implementation approach (wrapping the OSError in _cpu_percent / _memory_usage) is the right pattern. The code path is correct: on Linux procfs always works, so psutil is never reached.
One blocker before this can ship: psutil>=5.9 is currently in requirements.txt as a hard dependency, which means every WebUI install (including Linux servers where procfs is always available and psutil is never called) pulls in a binary wheel (~1 MB, C extensions) unconditionally.
The pattern in this repo for optional platform-specific deps is the edge-tts model: it's listed in requirements.txt as a comment (# pip install edge-tts if you want server-side TTS) but not added to the install list. The code then does a try/except ImportError in the actual call site.
Fix needed:
- Remove
psutil>=5.9fromrequirements.txt(or move it to the optional comment section matching theedge-ttspattern) - In
_cpu_percentand_memory_usage, changeimport_module("psutil")to atry/except ImportErrorthat raisesRuntimeError("psutil_unavailable")when psutil isn't installed — the panel already handlesRuntimeErrorgracefully (shows "unavailable" for that metric) - Update the test to cover the "psutil not installed" case (monkeypatch
sys.modulesto simulate missing psutil) alongside the existing fallback test
With those changes, on a Linux server where psutil isn't installed: no behavioural change (procfs is always hit first). On macOS with psutil installed: the fallback works as designed. On macOS without psutil: the metric shows "unavailable" — acceptable.
Happy to re-review once updated. The underlying approach is sound; just the dependency declaration needs to match the opt-in pattern.
nesquena-hermes
left a comment
There was a problem hiding this comment.
Thanks for the PR @jkobject — the macOS procfs fallback is a real gap, and the implementation approach (wrapping the OSError in _cpu_percent / _memory_usage) is the right pattern. The code path is correct: on Linux procfs always works, so psutil is never reached.
One blocker before this can ship: psutil>=5.9 is currently in requirements.txt as a hard dependency, which means every WebUI install (including Linux servers where procfs is always available and psutil is never called) pulls in a binary wheel (~1 MB, C extensions) unconditionally.
The pattern in this repo for optional platform-specific deps is the edge-tts model: it's listed in requirements.txt as a comment (# pip install edge-tts if you want server-side TTS) but not added to the install list. The code then does a try/except ImportError in the actual call site.
Fix needed:
- Remove
psutil>=5.9fromrequirements.txt(or move it to the optional comment section matching theedge-ttspattern) - In
_cpu_percentand_memory_usage, changeimport_module("psutil")to atry/except ImportErrorthat raisesRuntimeError("psutil_unavailable")when psutil isn't installed — the panel already handlesRuntimeErrorgracefully (shows "unavailable" for that metric) - Update the test to cover the "psutil not installed" case (monkeypatch
sys.modulesto simulate missing psutil) alongside the existing fallback test
With those changes, on a Linux server where psutil isn't installed: no behavioural change (procfs is always hit first). On macOS with psutil installed: the fallback works as designed. On macOS without psutil: the metric shows "unavailable" — acceptable.
Happy to re-review once updated. The underlying approach is sound; just the dependency declaration needs to match the opt-in pattern.
Summary
psutilfallback for aggregate CPU/RAM metrics when Linux procfs is unavailable/procpath with a regression testTest plan
./scripts/test.sh tests/test_issue693_system_health_panel.py✅ (8 passed)ruff check api/system_health.py tests/test_issue693_system_health_panel.py✅git diff --check✅./scripts/test.shran locally:9318 passed, 103 skipped, 1 xfailed, 2 xpassed; 4 failures observed from local environment/config unrelated to this patch:bot_nameisClawdinstead of defaultHermesrequests, etc.)hermes_stateimport path picks up an incomplete local Hermes Agent checkout