Skip to content

Add on-demand CPU/network observation window (Phase 5-1) - #72

Merged
heznpc merged 2 commits into
mainfrom
feat/cpu-network-observation
Aug 13, 2026
Merged

Add on-demand CPU/network observation window (Phase 5-1)#72
heznpc merged 2 commits into
mainfrom
feat/cpu-network-observation

Conversation

@heznpc

@heznpc heznpc commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Summary

  • New "CPU·네트워크 관찰" (CPU/network observation) section on the Activity page. The owner picks a window (30s/1min/2min/5min) and triggers idle_cpu.sh and network_watch.sh concurrently over the same period, showing real CPU deltas and any newly-appeared connections/listening ports.
  • Investigated the existing options before choosing this shape (see storage_watch.sh/schedule.sh/idle_cpu.sh/network.sh and Windows' monitor.ps1):
    • schedule.sh is entirely hardcoded to one job (storage-watch's own label/plist/wrapper) -- not a generic multi-job scheduler. Extending it to CPU/network would mean building new scheduling infrastructure in the most security-sensitive part of the codebase (launchd install, SHA-256 script pinning, owner-approval gates), for signals (CPU/network) that are fast and bursty, unlike free disk space which changes slowly enough for an hourly cadence to make sense.
    • Windows' own scripts/monitor.ps1 is the existing cross-platform precedent for "CPU monitoring" in this app, and it's synchronous/foreground/user-triggered too -- confirmed via grep, no scheduling mechanism exists there either.
    • This keeps the change purely additive: no new scheduling surface, no new persisted state, reuses idle_cpu.sh completely unchanged (just a longer --window than the scan's own 3s default).
  • New scripts/network_watch.sh mirrors idle_cpu.sh's two-sample delta pattern. Identity for established connections is (process, remote host:port), deliberately ignoring the local ephemeral port -- an ordinary reconnect to an already-seen server assigns a new local port every time, and counting that as a "new connection" would be noise, not signal. Listening ports use the bound address:port directly since that itself is the signal.

Test plan

  • pytest tests/ -- 358 passed (incl. 5 new test_macos_network_watch.py cases)
  • swift test -- 157 passed (incl. 5 new ObservationServiceTests)
  • python3 scripts/release_smoke.py --check-only -- {"ok": true}
  • Every new test proven via revert/restore, including one that caught a real gap in my own fixture: the local-port-ignore behavior was tested by swapping in the wrong key, and it caught 3 failures (including the "no changes" case I hadn't specifically targeted) -- fixed and reconfirmed
  • Verified end-to-end in the actual signed app, not just unit tests: built via build_macos_swift_app.sh (after Fix build_macos_swift_app.sh silently omitting shipped scripts #71 fixed the runtime bundle, without which this couldn't have worked at all), launched it, triggered a real 30-second observation, and confirmed genuine results -- real CPU usage from processes actually running on this machine, and real new connections detected (a local loopback connection from a dev tool, an HTTPS connection to a real external endpoint), correctly rendered with the right icons and labels.

Depends on

#71 (fixed the app's runtime bundle, which this PR's own scripts need to actually ship)

🤖 Generated with Claude Code

heznpc added 2 commits August 13, 2026 18:43
New "CPU·네트워크 관찰" section on the Activity page: the owner picks
a window (30s-5min) and triggers idle_cpu.sh and network_watch.sh
concurrently over the same period, showing real CPU deltas and any
newly-appeared connections/listening ports.

Scoped this as synchronous and user-triggered rather than a scheduled
background watch after investigating the existing options: schedule.sh
is entirely hardcoded to one job (storage-watch's own label/plist),
not a generic scheduler, so extending it would mean building new
scheduling infrastructure in the most security-sensitive part of the
codebase. CPU/network are also fast, bursty signals where a fixed
hourly sample would almost always just catch them idle -- unlike free
disk space, which changes slowly enough for that cadence to make
sense. Windows' own scripts/monitor.ps1 is the existing cross-platform
precedent for this shape: synchronous, foreground, user-triggered,
never scheduled.

New scripts/network_watch.sh mirrors idle_cpu.sh's two-sample delta
pattern: identity for established connections is (process, remote
host:port), ignoring the local ephemeral port, so an ordinary
reconnect to an already-seen server isn't reported as "new" -- proven
with a fixture and a revert/restore of the fix (it also caught the
"no changes" case, not just the one it targeted). idle_cpu.sh needed
no changes at all; it's invoked with a longer window than the scan's
own 3s default, same script either way.

Verified end-to-end in the actual signed app (built via
build_macos_swift_app.sh, after #71 fixed the runtime bundle): the
window picker, in-flight state, and results all render correctly, and
triggering it for real over a 30s window surfaced genuine CPU usage
and network connections happening on this machine during the run.
network_watch.sh checks uname -s before its PCH_TEST_MODE branch, same
as idle_cpu.sh -- the injection path is unreachable on Linux, so these
tests need the same skipif guard test_macos_idle_cpu.py already uses
for its own analogous tests. Missed this the first time; the argument-
validation test doesn't need it since that check runs before the
platform gate.
@heznpc
heznpc merged commit c624916 into main Aug 13, 2026
8 checks passed
@heznpc
heznpc deleted the feat/cpu-network-observation branch August 13, 2026 10:04
heznpc added a commit that referenced this pull request Aug 13, 2026
…73)

Two bugs in lsof output handling, both confirmed against this
machine's real output rather than hypothesized:

1. lsof escapes a space inside a COMMAND name as literal "\x20"
   ("Codex " -> "Codex\x20") -- that escaping is what keeps whitespace
   field-splitting correct, but passed through verbatim it leaked into
   the UI: the main scan's network/listeningPorts sections (present
   since the JXA port, "Codex\x20"/"Manus\x20" in this machine's real
   scan_result.json) and network_watch.sh's rows (#72). Both now
   unescape it, and drop the trailing space left by lsof's 9-character
   truncation -- invisible in the UI while still splitting dedup keys.

2. network_watch.sh's awk used the FNR==NR idiom to separate the two
   samples. With an empty first sample -- a real shape, since lsof
   exits 1 with no output both on failure and on zero matches, and
   `|| true` swallows it -- FNR==NR misreads the second file as the
   first, so every connection made during the window was registered as
   "already seen" and reporting went silent. Found because the new
   escaping test's fixture happened to use an empty first listen
   sample. Replaced with POSIX inter-file variable assignment
   (building=1 file1 building=0 file2), which holds even for empty
   files; with no baseline, over-reporting is the safe direction.

All three new tests proven against the pre-fix code via stash/restore.

Co-authored-by: Heznpc <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant