redbear-power: v1.42 CPU affinity
The next item from the v1.41 deferred list: read
/proc/<pid>/status:Cpus_allowed_list and display it as
both a single-char row indicator and a full expanded
list in the PID detail popup. htop parity.
Kernel format
The kernel emits the list as comma-separated ranges:
"0-3,5,7-11" means CPUs 0, 1, 2, 3, 5, 7, 8, 9,
10, 11
Cpus_allowed_list is the HARD affinity mask (settable
via sched_setaffinity(2)). v1.42 reads it because it
matches what an operator sees with 'taskset'.
New functions
- read_cpu_affinity(pid): parses the kernel string
- parse_cpu_list(s): public, testable parser
- format_cpu_list(ids): inverse of parse_cpu_list
- read_cpu_affinity_for_pid(pid): pub wrapper for the
PID detail popup
Two display modes
- Process panel row: '*' (subset), ' ' (all CPUs),
'?' (unknown). Single char so COMM stays visible.
- PID detail popup: full range string + expanded
Vec (truncated to 8 items on large machines).
New field on ProcessInfo
- cpu_affinity: Option<Vec<u32>>
Robustness
- Whitespace tolerated
- Out-of-order or duplicate IDs deduped and sorted
- Non-numeric chunks silently dropped
- Reversed ranges (start > end) silently dropped
- Empty input returns empty Vec (popup distinguishes
'no data' / None vs 'explicitly empty' / Some(empty))
Tests
- 13 new tests (11 in process.rs for parse/format/
read, 1 self-affinity test, 1 missing-pid test).
- 183/183 tests pass (was 170 in v1.41).
The improvement plan doc is also updated with §66
covering the v1.42 architecture, kernel format, the
two display modes, the parse/format inverse pair, and
the v1.43 deferred list.
This commit is contained in:
@@ -5697,6 +5697,99 @@ dir within a single refresh cycle.
|
||||
per-thread, so this would be a synthetic derivation.
|
||||
Defer to v1.42 if user demand appears.
|
||||
|
||||
## 66. v1.42 CPU Affinity (2026-06-21)
|
||||
|
||||
The next item from the v1.41 deferred list: CPU affinity
|
||||
from `/proc/<pid>/status:Cpus_allowed_list`. htop has
|
||||
this as a column; v1.42 ships it as both a single-char
|
||||
row indicator (Process panel) and a full expanded list
|
||||
(PID detail popup).
|
||||
|
||||
### 66.1 Kernel format
|
||||
|
||||
The kernel emits the list as comma-separated ranges:
|
||||
|
||||
```
|
||||
0-3,5,7-11 means CPUs 0, 1, 2, 3, 5, 7, 8, 9, 10, 11
|
||||
```
|
||||
|
||||
`Cpus_allowed_list` is the **hard** affinity mask
|
||||
(settable via `sched_setaffinity(2)`). The kernel also
|
||||
exposes `Cpus_allowed` (same format, but only the
|
||||
effective subset — without isolated CPUs that exist
|
||||
but aren't allowed for this process). v1.42 reads
|
||||
`Cpus_allowed_list` because it matches what an operator
|
||||
sees when they set the affinity with `taskset`.
|
||||
|
||||
### 66.2 Two display modes
|
||||
|
||||
| Location | Format | Why |
|
||||
|----------|--------|-----|
|
||||
| Process panel row | `*` (subset) / ` ` (all CPUs) / `?` (unknown) | Single char so it doesn't push COMM off the visible area. |
|
||||
| PID detail popup | Full range string + expanded list | Operators debugging thread pinning need the exact list. |
|
||||
|
||||
The `*` indicator fires when the affinity list is
|
||||
**shorter** than the host's CPU count. We can't compare
|
||||
specific IDs (host CPUs may have non-contiguous IDs on
|
||||
NUMA systems) so the comparison is count-based. On
|
||||
machines with hot-pluggable CPUs, the host CPU count
|
||||
changes over time, and the indicator might briefly show
|
||||
`*` for a process with the full mask. The popup shows
|
||||
the truth.
|
||||
|
||||
### 66.3 `parse_cpu_list` and `format_cpu_list`
|
||||
|
||||
Inverse pair, both `pub` for testability:
|
||||
|
||||
```rust
|
||||
parse_cpu_list("0-3,5,7-11") == [0, 1, 2, 3, 5, 7, 8, 9, 10, 11]
|
||||
format_cpu_list(&[0,1,2,3,5,7,8,9,10,11]) == "0-3,5,7-11"
|
||||
```
|
||||
|
||||
Robustness:
|
||||
|
||||
- Whitespace tolerated (`" 0-3 , 5 "` parses correctly)
|
||||
- Out-of-order or duplicate IDs are deduped and sorted
|
||||
- Non-numeric chunks silently dropped (kernel never
|
||||
emits these, but a corrupt procfs might)
|
||||
- A range with start > end silently dropped
|
||||
- Empty input returns empty Vec (popup distinguishes
|
||||
"no data" / None vs "explicitly empty" / Some(empty))
|
||||
|
||||
### 66.4 Tests
|
||||
|
||||
| Test | What it verifies |
|
||||
|------|------------------|
|
||||
| `parse_cpu_list_basic` | Single range expands correctly. |
|
||||
| `parse_cpu_list_mixed_ranges_and_singletons` | The canonical mixed format. |
|
||||
| `parse_cpu_list_handles_whitespace` | Whitespace tolerated. |
|
||||
| `parse_cpu_list_dedupes_and_sorts` | Out-of-order and duplicate IDs are deduped. |
|
||||
| `parse_cpu_list_silently_drops_malformed_chunks` | Non-numeric and reversed ranges dropped. |
|
||||
| `parse_cpu_list_empty_returns_empty` | Empty input returns empty Vec. |
|
||||
| `format_cpu_list_basic` | Contiguous range collapses to "start-end". |
|
||||
| `format_cpu_list_mixed` | Mixed ranges and singletons. |
|
||||
| `format_cpu_list_empty` | Empty input returns "". |
|
||||
| `format_cpu_list_single_id` | Single CPU ID renders without range dash. |
|
||||
| `parse_and_format_round_trip` | parse → format produces the original kernel string (4 cases). |
|
||||
| `read_cpu_affinity_handles_self` | Test runner's own affinity is non-empty + sorted. |
|
||||
| `read_cpu_affinity_returns_none_for_missing_pid` | None for non-existent PID. |
|
||||
|
||||
**183/183 tests pass as of v1.42.**
|
||||
|
||||
### 66.5 What was NOT changed (intentional)
|
||||
|
||||
- **History reclaim LRU** — defer to v1.43. Even at
|
||||
thousands of short-lived procs, each `VecDeque<u8>` is
|
||||
~24 bytes; the LRU cap is a "polish" feature, not a
|
||||
"prevents OOM" feature.
|
||||
- **Per-thread CPU%** (synthetic) — defer to v1.43 if
|
||||
user demand appears. The Linux kernel only exposes
|
||||
process-total CPU%, not per-thread.
|
||||
- **CPU affinity setter (taskset-style keypress)** —
|
||||
defer to v1.43. The reader side is in v1.42; the
|
||||
writer side requires an ioctl wrapper that we don't
|
||||
have yet.
|
||||
|
||||
## 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