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:
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user