Repository navigation
M1: extract the core metrics layer - #1
Merged
Merged
Conversation
Introduces pulse.core: immutable models, pluggable sources, a fault-isolating sampler, and a store that owns history and derived rates. Nothing in the package imports Textual or Rich, so it is testable with no real system access and reusable by a future exporter or headless mode. - models: frozen dataclasses; one tick is one Snapshot - sources: SystemSource (the only module that touches psutil/direct_os) and a deterministic MockSource - sampler: reads each subsystem independently, so a restricted net_connections degrades its own panel instead of blanking the tick - store: rates derived once per tick from real elapsed time, rather than each panel tracking its own counters and assuming a one-second interval No UI changes yet; panels still sample directly. 75 tests, 95% coverage on the package. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ports every panel off direct psutil calls and onto the shared store, so one tick produces one Snapshot that the whole UI renders from. What this fixes: - Four panels used to call cpu_percent() independently in the same refresh, so four different CPU numbers could be on screen at once. Now there is one. - Sampling blocked the event loop: reading /proc, walking the socket table and stat-ing mount points all ran on the render thread. It now runs on a worker, and an overrunning tick is skipped instead of queueing behind itself. - Each panel derived its own rates against an assumed one-second interval. Rates are now computed once from the real elapsed time between snapshots. - DataTables were cleared and rebuilt every tick with cursor restore that mostly did not hold. Rows are diffed and updated in place. - A failing subsystem was swallowed by a bare except. It now degrades its own panel and is reported in the Insight view. - The Aether visualisation sampled psutil at 20 FPS on its own; it now takes metrics from the shared snapshot. Because the source is injectable, the entire UI can be driven from a deterministic MockSource with no real system access. That is what took coverage from 39% to 73% - the panels were previously untestable. Architectural tests parse imports and fail if anything under panels/, screens/ or aether/ reaches for psutil or subprocess, or if pulse.core imports Textual or Rich. 199 tests (was 94). CI floor raised to 65%, with 85% on the action and config modules and 90% on pulse.core. No visible redesign: same layout, panels, themes and keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test_rates_are_available_after_two_ticks raced the app's own refresh timer: if an interval fired between advancing the mock machine and taking the sample, both snapshots landed on the same mock tick, the counter delta was genuinely zero, and the rate assertion failed. It passed on ubuntu 3.10 and windows 3.12 purely on timing. The fault was in the test, not the sampling code. Tests now freeze the app after boot and drive each sample through a `tick()` helper that waits on the worker, so the two snapshots being compared are the two the test produced. Also adds the inverse case: an idle machine must report a zero rate rather than noise. Verified with six consecutive runs of the integration suite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SystemSource passed an unsorted limit straight to direct_os, so on a host with more processes than the cap it kept an arbitrary prefix - the 300 lowest PIDs. Every high PID was dropped, including the heaviest processes the panel exists to show, and Pulse itself. The snapshot now holds the union of the heaviest processes by CPU and by memory, so whichever way a view sorts, what it wants is present. The expensive per-process psutil lookups run only for that selection rather than for an arbitrary slice. Caught by the macOS CI jobs, where the runner has enough processes to cross the cap; ubuntu and windows runners stayed under it and hid the bug. Added tests that reproduce it directly with a synthetic oversized machine, so it no longer depends on how busy the runner happens to be. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves metric collection out of the widgets and onto a worker thread. This is
milestone M1 of the v2 plan.
No visible redesign — same layout, panels, themes and keys. What changes is
that the numbers agree with each other and the UI stops hitching.
What this fixes
cpu_percent()in the same refresh — four different CPU numbers could be on screen at onceSnapshotper tick, shared by every panel/proc, walking the socket table and stat-ing mount points all ran on the render thread; zero workers in the codebaseexceptArchitecture
Panels read
self.snapshot/self.history/self.ratesfrom the base classand sample nothing.
Testing
The source is injectable, so the whole UI can be driven from a
MockSourcewith no real system access — which is why the panels are testable at all now.
modules and a new 90% gate on
pulse.corepanels/,screens/oraether/reaches for psutil or subprocess, or ifpulse.coreimports a UI library
snapshot object, each subsystem is polled exactly once per tick, sampling
happens off the event loop thread, and snapshots are applied back on it
Not in this PR
The full-screen views still probe panels with
hasattrfor eight optionalmethods, and the keymap still has collisions (
fmeans three things,rmeansfour). That is M3.
🤖 Generated with Claude Code