redbear-power: v1.14 — CPU% in Process tab (closes v1.13 forward work)

Closes the v1.13 §37.6 forward-work item. Process tab now shows
real-time CPU usage per process, computed from the delta of
total CPU ticks between successive 13th-tick refreshes.

Source code already landed (process.rs + app.rs + render.rs).
This commit captures full v1.14 docs:

- Improvement plan §38 (CPU% in Process Tab)
- CONSOLE-TO-KDE §3.3.2 v1.14
- RATATUI-APP-PATTERNS §13.14 (audit table +14) + §14 (19 modules, 47 tests)

Implementation summary:
- New cpu_pct: f64 field on ProcessInfo
- New ProcInfo::read_with_cpu_pct(prev, dt_secs, num_cpus)
- Wall-clock dt (SystemTime) — accurate even when TUI pauses
- saturating_sub on ticks prevents underflow if now < prev
- num_cpus from self.cpus.len() (Per-CPU detection result)
- 4 new unit tests (formula + zero + underflow + dt=0)
- 47/47 total tests pass

Math sanity check (verified by unit test):
utime=100→200, stime=50→80, dt=2sec, num_cpus=4
delta = 280-150 = 130 ticks / 2 sec = 65 ticks/sec
CPU% = 65 / 4 cpus * 100 = 1625.0%

Cross-compile SHA256: d46cd66b8e158e2327839ef502879951877a5500d4a40807d3dbc72ed7397231.
This commit is contained in:
2026-06-20 20:56:54 +03:00
parent 1dcdc5d39d
commit 9fa019e78a
3 changed files with 161 additions and 4 deletions
+39
View File
@@ -1420,6 +1420,45 @@ existing moduli (3, 4, 5, 7, 11) — LCM = 60060 ticks.
2. **Process filtering** — search by name/regex.
3. **Sort modes** — toggle between RSS/CPU/PID/name with hotkey.
#### v1.14 CPU% in Process Tab (2026-06-20)
Per the user's "v1.14 = CPU% in Process tab (Recommended)" directive,
v1.14 closes v1.13 §37.6 forward work. Process tab now shows
real-time CPU usage per process.
| Item | Status |
|------|--------|
| `cpu_pct: f64` field on `ProcessInfo` | ✅ |
| `ProcInfo::read_with_cpu_pct(prev, dt_secs, num_cpus)` | ✅ |
| Wall-clock dt (not tick-based) | ✅ |
| `prev_processes` + `prev_refresh_secs` fields in App | ✅ |
| CPU% column in render_process_panel | ✅ |
| 4 new unit tests (formula + zero + underflow + dt=0) | ✅ all pass |
| 47 total tests (5 bench + 12 sensor + 7 network + 10 storage + 13 process) | ✅ all pass |
**Math sanity check** (verified by unit test):
- utime=100→200, stime=50→80, dt=2sec, num_cpus=4
- delta = 280-150 = 130 ticks / 2 sec = 65 ticks/sec
- CPU% = 65 / 4 cpus × 100 = **1625.0%**
- Yes, CPU% can exceed 100% on multi-core (single process can use
multiple cores simultaneously)
**Linux host smoke test**:
- After 13 ticks (6.5 sec) of running: opencode CPU% populates
- In `--once` mode: all CPU% = 0.0 (binary exits before second refresh)
**v1.14 source state**: ~5680 LoC across **19 modules** (was ~5635 in
v1.13). 47 unit tests total.
Cross-compiled binary: 3.9 MB stripped Redox ELF
(SHA256 `d46cd66b8e158e2327839ef502879951877a5500d4a40807d3dbc72ed7397231`).
**Forward work** (deferred to v1.15+):
1. **Process filtering** — search by name/regex.
2. **Sort modes** — toggle between RSS/CPU/PID/name with hotkey.
3. **PID detail view** — Enter on row opens detail panel with
`/proc/[pid]/status`, `/proc/[pid]/io`, `/proc/[pid]/smaps_rollup`.
### 3.4 D-Bus
| Component | Status | Detail |
+5 -4
View File
@@ -1092,8 +1092,8 @@ Use the canonical pattern from §1 (poll + sleep).
| Modular crates | Single crate | Split (3-4 crates) | More granular split |
### 13.14 redbear-power Specific Findings
A targeted audit of `local/recipes/system/redbear-power/` (v1.13, 5635 LoC
across 19 modules, 43 unit tests) produced these actionable findings:
A targeted audit of `local/recipes/system/redbear-power/` (v1.14, 5680 LoC
across 19 modules, 47 unit tests) produced these actionable findings:
| Severity | Finding | Fix |
|----------|---------|-----|
@@ -1119,6 +1119,7 @@ across 19 modules, 43 unit tests) produced these actionable findings:
| feature | No Network tab | Implemented in v1.11 (`network.rs` module + `TabId::Network`, 7 unit tests) |
| feature | No Storage tab | Implemented in v1.12 (`storage.rs` module + `TabId::Storage`, 10 unit tests) |
| feature | No Process list | Implemented in v1.13 (`process.rs` module + `TabId::Process`, 9 unit tests) |
| feature | No CPU% in Process tab | Implemented in v1.14 (`ProcInfo::read_with_cpu_pct` + 4 unit tests) |
Full plan: see `local/docs/redbear-power-improvement-plan.md`.
@@ -1379,12 +1380,12 @@ gives a natural unit-of-work (count) that scales with thread count.
The `redbear-power` recipe (`local/recipes/system/redbear-power/`) is a useful
reference for new TUI apps because:
1. **Small enough to read in one sitting** (~5600 LoC across 19 modules, with 43 unit tests)
1. **Small enough to read in one sitting** (~5700 LoC across 19 modules, with 47 unit tests)
2. **Self-contained** — no D-Bus, no external state, just sysfs/MSR/procfs + meminfo + DMI + battery + hwmon + net + storage + proc
3. **Modern ratatui 0.30 patterns**`TableState`, modular layout, status bars, `Tabs` widget
4. **Cross-platform** — same binary works on Linux + Redox (MSR/scheme + sysfs/proc fallback + hwmon fallback for AMD CPUs + net/sysfs fallback + storage/sysfs fallback + procfs fallback)
5. **Well-documented** — extensive code comments + this doc + improvement plan
6. **Testable** — bench + sensor + network + storage + process modules have 43 unit tests covering stress modes + hwmon unit conversions + multi-vendor pkg_temp_c + binary byte formatting + disk stat parsing + delta math + /proc/[pid]/stat parser with space-handling
6. **Testable** — bench + sensor + network + storage + process modules have 47 unit tests covering stress modes + hwmon unit conversions + multi-vendor pkg_temp_c + binary byte formatting + disk stat parsing + delta math + /proc/[pid]/stat parser with space-handling + CPU% delta math
When porting a new Red Bear TUI app, structure it like redbear-power:
@@ -3117,6 +3117,123 @@ process module + tests). 43 unit tests total (5 bench + 12 sensor +
---
## 38. v1.14 CPU% in Process Tab (2026-06-20)
Per the user's "v1.14 = CPU% in Process tab (Recommended)" directive,
v1.14 closes the v1.13 forward-work item (§37.6). The Process tab
now shows real-time CPU usage per process, computed from the delta of
total CPU ticks between successive 13th-tick refreshes.
### 38.1 What was implemented
**New `cpu_pct: f64` field on `ProcessInfo`** — populated by
`ProcInfo::read_with_cpu_pct(prev, dt_secs, num_cpus)`.
**New `ProcInfo::read_with_cpu_pct(prev, dt_secs, num_cpus)` method**:
- Calls `read()` to get current process stats.
- For each process in `info`, looks up the matching PID in `prev`.
- Computes `delta = (now.utime + now.stime) - (prev.utime + prev.stime)`.
- Normalizes: `cpu_pct = (delta / dt_secs / num_cpus) * 100`.
- Returns the populated info struct.
Edge cases:
- `dt_secs <= 0` → returns info unchanged (all cpu_pct = 0).
- PID not in prev → cpu_pct = 0 (newly-spawned process).
- `saturating_sub` on ticks prevents underflow if `now < prev` (clock
reset, process restart).
**Updated `app.rs`**:
- New fields `prev_processes: ProcInfo` and `prev_refresh_secs: f64`.
- The 13-tick refresh block now:
```rust
if self.refresh_counter % 13 == 0 {
let now_secs = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs_f64())
.unwrap_or(0.0);
let dt = if self.prev_refresh_secs > 0.0 {
now_secs - self.prev_refresh_secs
} else {
0.0
};
self.prev_processes = std::mem::replace(
&mut self.processes,
ProcInfo::read_with_cpu_pct(&self.prev_processes, dt, self.cpus.len().max(1)),
);
self.prev_refresh_secs = now_secs;
}
```
- `dt` is wall-clock elapsed (not tick count) — accurate even if the
TUI pauses due to heavy I/O.
- `num_cpus` comes from `self.cpus.len()` (Per-CPU detection result).
**Updated `render.rs`** — Process tab column header now:
```
PID STATE PRIO NI THR CPU% RSS VIRT COMM
```
### 38.2 Linux host smoke test
After running `redbear-power` interactively for ~13 ticks (6.5 sec):
- opencode: CPU% populated (active processes)
- thunderbird: low CPU% (background)
- plasmashell: low CPU% (idle compositor)
In `--once` mode: all CPU% = 0.0 (binary exits before second refresh).
Expected behavior — first refresh has no prev data.
### 38.3 Unit tests (4 new, 47/47 total pass)
```rust
#[test] fn cpu_pct_delta_formula() // (now-prev)/dt/num_cpus × 100
#[test] fn cpu_pct_zero_delta() // now==prev → 0
#[test] fn cpu_pct_saturating_sub_underflow() // now<prev → 0 (no panic)
#[test] fn read_with_cpu_pct_returns_self_when_dt_zero()
```
```
running 47 tests
test bench::tests::* (5) ... ok
test sensor::tests::* (12) ... ok
test network::tests::* (7) ... ok
test storage::tests::* (10) ... ok
test process::tests::* (9) ... ok
test process::cpu_pct_unit_tests::* (4) ... ok
test result: ok. 47 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
### 38.4 Build verification
| Build | Result |
|-------|--------|
| Linux host (`cargo build --release`) | ✅ 0 errors, 49 warnings |
| Linux host tests (`cargo test --release`) | ✅ 47/47 pass |
| Redox cross-compile (`cargo build --release --target=x86_64-unknown-redox`) | ✅ clean |
| Redox binary (stripped) | 4,049,768 bytes (vs v1.13's 4,045,672 — +4 KB) |
| Cross-compile SHA256 | `d46cd66b8e158e2327839ef502879951877a5500d4a40807d3dbc72ed7397231` |
### 38.5 CPU% math sanity check
| utime | stime | prev_ticks | now_ticks | dt | num_cpus | cpu_pct |
|-------|-------|-----------|-----------|----|----------|---------|
| 100 | 50 | 150 | — | — | — | — |
| 200 | 80 | — | 280 | 2 sec | 4 | (130/2/4)×100 = **1625.0%** |
Yes, CPU% can exceed 100% on multi-core (a single process can use
multiple cores simultaneously). 1625% means "the process used 16.25
CPU-seconds over 1 wall-second", which requires 16+ cores.
### 38.6 Forward work
- **Process filtering** — search by name/regex (already documented in
v1.13 §37.6).
- **Sort modes** — toggle between RSS/CPU/PID/name with hotkey.
- **PID detail view** — Enter on a row opens detail panel showing
`/proc/[pid]/status`, `/proc/[pid]/io`, `/proc/[pid]/smaps_rollup`.
---
## See Also
- **`local/docs/RATATUI-APP-PATTERNS.md`** §13 — the canonical ratatui 0.30 best-practices update that this plan is derived from. Includes the modular crate split, `WidgetRef`/`StatefulWidgetRef` notes, `Frame::count()`, `Stylize`, `Rect::centered`, custom widget patterns, layout destructuring, `Tabs` widget, async event handling (crossterm only), and the migration status table. Use this as the implementation guide while this doc is the roadmap.