Refresh puter UI, power telemetry and demand-aware monitoring
This commit is contained in:
+132
@@ -0,0 +1,132 @@
|
||||
# Release profiling — 22 September 2026
|
||||
|
||||
## Setup
|
||||
|
||||
Apple M4 Pro, 48 GiB RAM, macOS 27.0 (26A428), Xcode 27.
|
||||
Optimized SwiftPM Release build of the current uncommitted working tree.
|
||||
Isolated ad-hoc signed app: `/private/tmp/puter-profile-release.app`.
|
||||
Processes page, default Normal cadence, grouped categories, about 650–690 processes.
|
||||
Installed application was not replaced. No application source was changed during profiling.
|
||||
|
||||
## Measurements
|
||||
|
||||
Each counter measurement lasted 20 seconds, without Instruments attached.
|
||||
CPU is a percentage of **one core**, unlike puter's machine-normalized CPU column.
|
||||
Counters came from `proc_pid_rusage` v4, converting CPU Mach ticks with
|
||||
`mach_timebase_info`; memory is physical footprint, not resident size.
|
||||
|
||||
| State | CPU (one core) | Interrupt wake-ups / 20 s | Physical footprint, start → end |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Processes visible, updating | 9.032% | 35 | 66.55 → 65.72 MiB |
|
||||
| After minimize action (activity not independently verified) | 8.992% | 52 | 71.64 → 66.84 MiB |
|
||||
| Hidden/inactive, menu-bar consumer enabled | 8.929% | 58 | 70.09 → 69.81 MiB |
|
||||
| Paused | 0.014% | 3 | 69.83 → 69.36 MiB |
|
||||
|
||||
For the hidden measurement, NSRunningApplication reported `isActive=false` and
|
||||
`isHidden=true`; those flags were checked again after its separate trace.
|
||||
The 3-second `sample` capture reported 81.6 MiB footprint, 95.4 MiB lifetime peak.
|
||||
These short observations do not establish a memory leak or long-term plateau.
|
||||
|
||||
## Trace-backed findings
|
||||
|
||||
Two separate 15-second Time Profiler traces were captured. Percentages below are
|
||||
inclusive sampled CPU weights; nested paths overlap and must not be added.
|
||||
|
||||
1. **Repeated process grouping dominates the UI work.** In the visible trace,
|
||||
85.9% of sampled CPU weight belonged to the main thread; 42.0% passed through
|
||||
`ProcessesView.grouped`, and 29.6% through `ProcessGrouping.appPath(for:)`.
|
||||
`displayedGroups` invokes this computed property from category filtering,
|
||||
table rendering and empty-state checks. Each computation enumerates running
|
||||
applications, groups processes, parses app paths, and sorts the result.
|
||||
Next fix: prepare one stable grouped/sorted snapshot per relevant input change,
|
||||
cache executable-path → application identity, and reuse category partitions.
|
||||
|
||||
2. **Background demand is not behaving as intended at runtime.** The hidden trace
|
||||
still includes full `nativeProcessRecords` scans (5.1% inclusive), grouping
|
||||
(38.6%) and SwiftUI graph transactions (58.0%). CPU did not materially fall when
|
||||
hidden. Pure SamplingDemand unit tests therefore do not validate lifecycle
|
||||
integration. The precise reason for the stale demand is not established.
|
||||
Next fix: instrument scheduler demand transitions and connect them to actual
|
||||
window visibility/occlusion and application activity; add integration tests.
|
||||
|
||||
3. **Disk-capacity lookup remains in every telemetry capture.** `diskUsage()` is
|
||||
unconditional even on the lightweight path. It accounts for 8.4% of visible
|
||||
trace weight and 14.4% of hidden trace weight. It requests available capacity
|
||||
for important usage, which is not a high-frequency activity counter.
|
||||
Next fix: cache capacity and refresh it on a slower, disk-view-specific cadence.
|
||||
|
||||
4. **Pause provides a genuinely quiet baseline.** It reduced measured CPU to
|
||||
0.014% and produced no process-attributed disk I/O during the sample. This makes
|
||||
always-on rendering alone unlikely to explain the updating-state cost.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Visible trace: `/private/tmp/puter-foreground-release.trace`
|
||||
- Hidden trace: `/private/tmp/puter-background-release.trace`
|
||||
- Exported tables: `/private/tmp/puter-time-profile.xml`, `/private/tmp/puter-background-profile.xml`
|
||||
- Stack sample: `/private/tmp/puter-release-sample.txt`
|
||||
- Counter helper: `/private/tmp/puter-profile-counters.c`
|
||||
- Trace aggregation: `/private/tmp/analyze-puter-trace.py`
|
||||
|
||||
Traces are local only and may contain process names, paths and environment metadata.
|
||||
They were not uploaded. Temporary artifacts may be removed by macOS.
|
||||
|
||||
## Limits and next validation
|
||||
|
||||
This was a short live-machine study, not a before/after comparison against the old
|
||||
poller. Other applications and an earlier preview were running; system memory
|
||||
utilization was high. No GPU energy, scroll hitch rate or frame-time claim is made.
|
||||
Instrumented samples are used for attribution, not as the low-overhead CPU metric.
|
||||
|
||||
Fix lifecycle integration first, then repeated grouping and capacity lookup.
|
||||
Repeat these exact 20-second scenarios and add a SwiftUI scrolling trace before
|
||||
calling the performance work complete.
|
||||
|
||||
## Fix pass — 22 September 2026
|
||||
|
||||
Implemented window-visibility-driven demand with notification-driven rescheduling
|
||||
and live-state reconciliation before a scheduled sample. Visible unfocused windows
|
||||
remain live; hidden/minimized/occluded windows do not request process inventories.
|
||||
Recording and menu-bar consumers retain their separate demand policies.
|
||||
Initial load and explicit Refresh may request a single inventory.
|
||||
|
||||
Moved process grouping to inventory application, added a bounded app-path cache
|
||||
(including negative results), and shared each table projection across category
|
||||
rendering and empty-state checks. Cached disk capacity separately from activity
|
||||
counters, refreshing on initial/manual requests, volume enumeration and every
|
||||
60 seconds on visible Performance.
|
||||
|
||||
Release preview: `/private/tmp/puter-profile-fixed2.app`, PID 31379.
|
||||
32 tests passed, including new grouped-search, identity, totals and path-cache
|
||||
regressions. `git diff --check` passed. Installed app was not replaced.
|
||||
|
||||
| Scenario, 20 seconds | Before, one-core CPU | After, one-core CPU |
|
||||
| --- | ---: | ---: |
|
||||
| Processes visible, updating | 9.032% | 3.541% |
|
||||
| Hidden, menu-bar consumer enabled | 8.929% | 0.914% |
|
||||
|
||||
Visible wake-ups: 34; footprint 73.41 → 75.24 MiB. Hidden wake-ups: 19;
|
||||
footprint 141.45 → 118.56 MiB, after visiting Hardware. Hidden state was checked
|
||||
before and after the valid run (`active=false`, `hidden=true`). An earlier hidden
|
||||
attempt was discarded because the preview was externally changed to Hardware
|
||||
and unhidden mid-measurement. No memory-improvement claim is made.
|
||||
|
||||
Visible 15-second trace: 963 ms sampled CPU versus the earlier 2114 ms.
|
||||
`ProcessesView.grouped` fell from 42.0% to 2.8% of sampled weight;
|
||||
`ProcessGrouping.groups` was 6.5%, and `appPath` about 0.1% (previously 29.6%).
|
||||
No `diskUsage` stack was sampled. Percentages are inclusive, overlapping weights.
|
||||
Trace: `/private/tmp/puter-fixed-visible.trace`; export:
|
||||
`/private/tmp/puter-fixed-visible.xml`.
|
||||
|
||||
The separate hidden trace sampled 300 ms CPU in 15 seconds (previously 1444 ms).
|
||||
It contained no sampled process-inventory, process-group construction or disk
|
||||
capacity stacks. The remaining sampled work is mostly SwiftUI/AppKit updates;
|
||||
menu-bar system counters intentionally remain enabled. Hidden state was again
|
||||
verified after capture. Evidence: `/private/tmp/puter-fixed-hidden.trace` and
|
||||
`/private/tmp/puter-fixed-hidden.xml`.
|
||||
|
||||
These are short live-machine observations, not a controlled benchmark. Process
|
||||
counts were around 700, system memory pressure differed from the first pass,
|
||||
and a test build ran during the visible counter sample. The savings are supported
|
||||
by the changed call stacks, but precise percentages will vary with workload.
|
||||
Scrolling/frame-time and long-term energy measurements remain separate work.
|
||||
Reference in New Issue
Block a user