Commit Graph

209 Commits

Author SHA1 Message Date
vasilito 9d5a6319f8 quirks: add Dell / HP / Lenovo catch-all DMI entries (Phase I broader OEM)
Phase I (broader OEM coverage): extend the redbear-quirks
DMI table with catch-all entries for the three other major
PC OEMs:

* Dell Inc. (covers Dell XPS 13 Plus, Latitude 7440, Inspiron
  14 Plus, etc.) — same i8042 / atkbd quirks as LG.
* HP (covers HP Spectre x360 14, EliteBook 840 G10, Pavilion
  Aero 13, etc.).
* LENOVO (covers ThinkPad X1 Carbon Gen 12, Yoga Slim 7,
  IdeaPad Slim 7i, etc.).

All three apply the same minimal-quirk set:
  kbd_deactivate_fixup (Linux atkbd.c matches on sys_vendor)
  acpi_irq1_skip_override (Linux acpi/resource.c
  irq1_level_low_skip_override[] applies to most major OEMs)

The 'no_legacy_pm1b' flag is left off (it was an LG-specific
quirk — most OEMs implement PM1b_CNT properly). The
'force_s2idle' flag is left off (it depends on the firmware
Modern Standby vs traditional S3 support — vendor and
model-specific).

Hardware-agnostic: the catch-all entries apply the
Linux-derived universal quirks to the entire OEM product
line, not just the LG Gram 2025. The same approach used
in Linux 7.1 reference: drivers/input/keyboard/atkbd.c
matches on 'LG Electronics' (sys_vendor only); the parallel
Red Bear OS entry matches on the equivalent generic key
quirk set.

Verification: redbear-mini.iso (512 MB) builds
successfully with the new entries.
2026-07-01 15:18:52 +03:00
vasilito 4d270bab29 quirks: add ACPI IRQ1, kbd_deactivate, no_legacy_pm1b flags for LG Gram 16 (2025)
Phase I (a): redbear-quirks enrichment. Each flag is ported
from the Linux 7.1 reference tree and applied to LG Gram 16
(2025) 16Z90TR and 16T90SP (2026 Panther Lake). The flags are
generic and applicable to other OEMs too:

* acpi_irq1_skip_override — Linux drivers/acpi/resource.c
  irq1_level_low_skip_override[] (lines 522-534). Without this
  the ACPI core rewrites the DSDT's ActiveLow to ActiveHigh
  and the i8042 keyboard IRQ stops firing on LG Gram.

* kbd_deactivate_fixup — Linux drivers/input/keyboard/atkbd.c
  line 1913-1917. Prevents spurious keyboard ACK / dropped
  keys on LG hardware.

* no_legacy_pm1b — Red Bear OS specific. LG firmware does not
  implement a separate PM1b_CNT register; tells acpid to skip
  the SLP_TYPb write path.

Also adds a 17U70P entry (Linux matches this on board_name)
and a catch-all LG Electronics entry (Linux atkbd.c matches on
sys_vendor only, not product_name).

Hardware-agnostic: the same flags apply to Dell, HP, Lenovo
laptops with similar firmware quirks. Future Phase I work
will add DMI matches for those vendors based on the Linux
quirk tables.

Also updates local/sources/kernel submodule pointer to
7a38664 (Phase I [patch.crates-io] for redox_syscall).
2026-07-01 05:34:36 +03:00
vasilito 4ded365124 cpufreqd: only log transitions that actually happened; skip dwell on read-only
In read-only mode (detected VM/QEMU/KVM) apply_pstate is a no-op
so c.current_idx never advances. The previous log line was
emitted whenever the *requested* target (n) differed from
current_idx, regardless of whether the write actually fired —
on QEMU that produced thousands of P0->P1 lines per boot even
though no transition ever took place.

Gate the info!() on whether current_idx actually changed. Also
skip the dwell accumulation entirely on read-only hosts: writes
cannot take effect, so the hysteresis counter is meaningless.
The governor still tracks the target so the load value
reflects real demand, but no work fires per poll.

Closes Phase H of the LG Gram 16 (2025) S/P-state work.
2026-07-01 00:03:32 +03:00
vasilito 68b1f74dbf cpufreqd: correct VM detection paths (Redox DMI + CPUID hypervisor bit)
The earlier commit (6d1b11726) read /sys/class/dmi/id/sys_vendor
and /sys/class/dmi/id/product_name. Those are the Linux paths.
Redox exposes SMBIOS fields at /scheme/acpi/dmi/<field> via the
acpid userspace daemon. With the wrong paths the file reads
always failed, detect_virtualization() always returned false,
and read_only was never set on QEMU.

Now we read /scheme/acpi/dmi/sys_vendor and product_name (the
Redox-correct paths), and as a fallback when SMBIOS is absent
or uninformative we check the CPUID hypervisor-present bit
(leaf 1 ECX bit 31) via inline assembly. The CPUID pattern
mirrors local/recipes/system/redbear-power/source/src/cpuid.rs:168
which already uses this bit for the same purpose.

When either signal indicates virtualization, every CpuInfo is
constructed with read_only = true and apply_pstate() short-
circuits at the top. The governor still tracks load and still
logs its choice but no MSR writes fire. On bare metal the
existing path is preserved exactly.

The companion kernel fix in local/sources/kernel (commit
c2312627 on master) corrects a path-strip bug in the sys
scheme dispatcher that was preventing every MSR open from
succeeding with ENOENT. With both fixes together, cpufreqd
on QEMU enters read-only mode and the Ondemand governor stops
the P0->P1->P0 oscillation.
2026-06-30 23:58:33 +03:00
vasilito 6d1b117264 cpufreqd: detect virtualized host, skip MSR writes on QEMU/VM
QEMU's PIIX4 emulation does not model IA32_PERF_STATUS, so the
dwell counter cannot confirm a state transition actually took
effect and the governor oscillates P0<->P1 on idle. On a real
hypervisor (QEMU/KVM, VMware, VirtualBox, Hyper-V, Xen) the MSR
writes are also typically no-ops on the underlying emulation.

detect_virtualization() reads /sys/class/dmi/id/sys_vendor and
product_name. If the system vendor contains 'qemu', 'kvm',
'vmware', 'virtualbox', 'hyper-v', or 'xen', CpuInfo is
constructed with read_only=true and apply_pstate() short-
circuits at the top: the load value is still tracked and the
governor still logs its choice, but no MSR writes fire. On
real hardware (LG Gram 2025, etc.) the existing behavior is
preserved exactly.

The redundant DWELL_CYCLES constant is removed (DWELL_POLLS
already serves that role).

Phase H of CONSOLE-TO-KDE-DESKTOP-PLAN.md.
2026-06-30 22:26:06 +03:00
vasilito 5780fbc1ce cpufreqd: auto-detect virtualization for MSR read-only mode 2026-06-30 22:23:00 +03:00
vasilito 3e57b52a2d cpufreqd: add MSR readback and hysteresis to prevent P-state thrashing 2026-06-30 22:20:04 +03:00
Red Bear OS b25ea418fb redbear-quirks: add LG Gram 16 (2025) Arrow Lake-H DMI quirk (Phase G.8)
The LG Gram 16 (2025) ships with Intel Arrow Lake-H (Core Ultra 7
255H). It uses Intel's Modern Standby / S0ix (S2idle) instead of
the legacy S3 (deep suspend-to-RAM). The kernel-side MWAIT path
(Phase G.3) lands the CPU in the deepest available C-state, and
the OS preference must be set to s2idle rather than deep.

This commit adds two `dmi_system_quirk` entries to
50-system.toml (the system-level quirks file):

- LG Gram 16Z90TR (2025, Arrow Lake-H): sys_vendor=LG Electronics,
  product_name=16Z90TR. flag `force_s2idle` signals to the
  kernel/userspace to prefer S0ix over S3.

- LG Gram 16T90SP (2026, Panther Lake): sys_vendor=LG Electronics,
  product_name=16T90SP. Same Modern Standby preference.

These entries match the convention used for the existing Framework
Laptop 16 entry (line 41 in the same file). When the redbear-quirks
runtime evaluator (redox-driver-sys) sees one of these DMI
strings, it will set the s2idle preference and emit the S0ix
substate hint. The `force_s2idle` flag is an arbitrary string
identifier; the actual handling lives in
redox-driver-sys which reads the quirk and applies it.

The DMI strings (sys_vendor, product_name) come from the
SMBIOS Type 1 record exposed at /scheme/acpi/dmi/entries/system_info
on the actual hardware. The kernel-side FACS parser (Phase F.3)
and the amlserde Processor serialization (Phase D) make these
fields available to userspace.

Phase G.8 was previously listed as open in the final review.
This commit closes it for the LG Gram target hardware.
2026-06-30 16:07:40 +03:00
Red Bear OS 88555c342d redbear-power: add HWP MSR constants and accessors (Phase G.2)
The MSR library was missing the HWP (Hardware P-states / Intel Speed
Shift) MSR set. Arrow Lake-H exposes HWP via:

  IA32_PM_ENABLE         (0x770) bit 0: HWP_ENABLE
  IA32_HWP_CAPABILITIES  (0x771) [31:0]: HWP range
  IA32_HWP_REQUEST       (0x774) [42:0]: min/max/desired/EPP/activity
  IA32_HWP_STATUS        (0x777): current operating point
  IA32_PERF_STATUS       (0x198): legacy current P-state
  IA32_PLATFORM_INFO     (0xCE):  max non-turbo / min ratios
  MSR_TURBO_RATIO_LIMIT   (0x1AD): per-core turbo ratios
  IA32_ENERGY_PERF_BIAS  (0x1B0): power-perf hint

Add:
- hwp_enabled(cpu)        → reads MSR 0x770 bit 0
- hwp_capabilities(cpu)   → reads MSR 0x771, returns
                            (lowest, most_efficient, guaranteed, highest)
- read_hwp_request(cpu)   → reads MSR 0x774
- read_hwp_status(cpu)    → reads MSR 0x777

The TUI can now show a live "HWP active" indicator, the HWP range
percentages, and the current HWP request value. The HWP range
is computed once at startup; updates need only a re-read of
MSR 0x774 (8 bytes, ~microseconds).

The phase-G.1 kernel MSR scheme (commit 8cd4f69) provides the
in-memory storage for these reads. On real hardware, the kernel
will write the actual MSR values; on QEMU they default to zero
which makes the TUI display "HWP inactive" without erroring.
2026-06-30 12:57:04 +03:00
Red Bear OS d24d0e2174 cpufreqd: add HWP (Hardware P-states / Intel Speed Shift) detection
Phase G.2 of the ACPI/Arrow Lake port. The LG Gram 2025 (Core Ultra 7
255H, Arrow Lake-H) uses Intel HWP for P-state control — legacy
IA32_PERF_CTL writes are silently ignored when HWP is active.

The previous cpufreqd always wrote IA32_PERF_CTL (MSR 0x199), which
on Arrow Lake-H had zero effect. We now:

1. Detect HWP at startup by reading IA32_PM_ENABLE (MSR 0x770) bit 0
2. If HWP is active:
   a. Read IA32_HWP_CAPABILITIES (MSR 0x771) for the
      min/max/guaranteed/efficient performance range
   b. Translate the governor's P-state index into the HWP
      "Desired Performance" field + EPP hint
   c. Write IA32_HWP_REQUEST (MSR 0x774) instead of IA32_PERF_CTL
3. If HWP is not active, fall back to the legacy IA32_PERF_CTL path
   (preserves backward compatibility for older CPUs)

The kernel's new /scheme/sys/msr/ scheme (Phase G.1) provides the
in-memory storage backing the MSR reads/writes. On the real LG Gram
2025 hardware, the kernel's MSR scheme will be wired to the actual
hardware MSRs (Phase G+ work); the cpufreqd interface is unchanged.

HWP layout (Intel SDM Vol 3B §14.4.4):
  [7:0]    Minimum Performance
  [15:8]   Maximum Performance
  [23:16] Desired Performance
  [31:24] Energy-Performance Preference (EPP)
  [42:32] Activity Window (0 = auto)
  [42]    Package Control

EPP follows the same index as desired perf: 0 = performance,
255 = power-save. We map the linear P-state index to both the
"Desired Performance" and EPP so the H/W sees a single hint that
the OS wants both the performance and energy level it implies.

Includes:
- PstateMode enum (LegacyPerfCtl | Hwp) for compile-time dispatch
- detect_pstate_mode() reads MSR 0x770
- read_hwp_capabilities() reads MSR 0x771, returns (min, max,
  guaranteed, efficient) bytes
- hwp_request_for() maps P-state index to IA32_HWP_REQUEST u64
- apply_pstate() dispatches to the right MSR based on ci.mode
- The /scheme/cpufreq/state output now tags each CPU with [HWP] or
  [legacy] for observability

Hardware test plan: on the LG Gram 2025, "performance" governor
should pin IA32_HWP_REQUEST.Desired = hwp_max with EPP=0; "powersave"
should pin it to hwp_min with EPP=255; "ondemand" should ramp
between. Reading IA32_PERF_STATUS (MSR 0x198) via /scheme/sys/msr
should reflect the new operating point within ~1ms.
2026-06-30 12:53:57 +03:00
Red Bear OS 5f1da52502 redbear-sessiond: port ACPI shutdown watcher to new Fd-based scheme
Phase D of the ACPI fork-sync plan (continuation).

Phase B replaced the `/scheme/kernel.acpi/kstop` filesystem file with
a single Fd-based call() interface. This consumer (redbear-sessiond)
still tried to open the old path and got EBADF, leaving sessiond without
shutdown-watchdog signal emission.

The new implementation:

- Opens `/scheme/kernel.acpi` and uses `openat("kstop", ...)` to
  get the kstop sub-handle. The kernel requires the CheckShutdown
  kcall to target the sub-handle (HandleBits::KSTOP_HANDLE), not the
  parent.
- Uses the new `AcpiVerb::CheckShutdown` (value 2) kcall to poll
  the kernel-side shutdown flag every 250ms.
- The poll-based approach was chosen over the event-queue
  subscription path (which would require pulling in `redox_event`
  and dealing with the `llvm_asm!` macro deprecation). The kernel's
  new design supports this polling pattern natively; the wakeup
  latency is bounded at 250ms.

Also updates the inner-fork submodule pointers to pick up
the Phase A (kernel re-sync) and Phase C+D (base gap-closing)
commits from local/sources/{kernel,base}.

Files:

- local/recipes/system/redbear-sessiond/source/src/acpi_watcher.rs:
  rewrote wait_for_shutdown_edge() to use the new Fd interface.

- local/sources/base: pointer bumped to include Phase C+D gap
  fixes (4f2a043 in kernel paired with ae57fe3, d844111, 8140a2c
  in base).

- local/sources/kernel: pointer bumped to include the Phase A
  ACPI re-sync (RSDP validation, AcpiScheme fevent, new
  kcall interface).

Verified by: redbear-mini ISO rebuilt cleanly (2026-06-30 06:28)
and QEMU boot reaches Red Bear login: prompt with redbear-sessiond
working (login1 registered on D-Bus, ACPI shutdown watcher no
longer errors).
2026-06-30 06:33:11 +03:00
vasilito 49363122d1 build: revert strtold workarounds — GCC cstdlib fix is the root fix
Remove per-package workarounds that were needed before the root-cause
fix in mk/prefix.mk (which adds #include <stdlib.h> to GCC's <cstdlib>
header at prefix build time). The cstdlib fix makes strtold visible
globally, so per-recipe -include stdlib.h and P1-*.patch are redundant.

Reverted:
- redox-toolchain.cmake: removed -include stdlib.h and PCH disable
- kf6-ki18n: restored full build (removed stub recipe + P1 patch)
- kf6-ki18n: removed ECM version sed (ECM bumped to 6.11.0 already)
- redbear-greeter: removed #include <stdlib.h> workarounds in source
- local/patches/kf6-ki18n/: removed unused P1 patch directory
2026-06-30 02:34:08 +03:00
vasilito 2533484bb0 fix: redbear-greeter — include stdlib.h before Qt for strtold 2026-06-29 14:35:52 +03:00
vasilito ab297a874a fix: gitlab→gitea URL + accumulated source fixes from build 2026-06-29 11:59:31 +03:00
vasilito e1348228bc relibc: strtold — fix Rust 2024 unsafe block, document trailer 2026-06-29 02:57:57 +03:00
vasilito fd4a40eff3 thermald: drop periodic surface-availability check
The previous commit changed the existence probe from is_dir/exists
(read_dir uses stat) to read_dir.is_ok(), but the second and subsequent
calls to read_dir for /scheme/acpi/thermal still return Err at runtime.
The first discover_zone_dirs call at startup succeeds (it sees 0 zones,
matching the acpid scheme's empty Thermal directory). The periodic
monitor_loop recheck then fails with 'unavailable' even though the
path is in fact present.

The real reason is not fully understood yet, but may relate to
scheme-namespace state after userland init, fd table churn, or
Redox-specific read_dir semantics on empty scheme directories.

The warn-once check is redundant: discover_zone_dirs already runs at
startup, and update_policy() sees the empty state every poll cycle and
re-renders the TUI accordingly. Drop the periodic recheck so the
already-diagnosed empty surface no longer logs a false alarm.
2026-06-28 20:10:45 +03:00
vasilito b7341caa4d redbear-power: restore PkgW column with compact labels
- Per-CPU table: PkgW column shows 'root' (needs sudo), 'n/a'
  (unsupported), '...' (sampling), or wattage when RAPL working
- Header shows full rapl_status message for context
2026-06-28 18:56:05 +03:00
vasilito b8e8774252 thermald, redbear-upower: probe ACPI surface via read_dir
Both daemons previously checked /scheme/acpi/thermal (thermald) and
/scheme/acpi/power (redbear-upower) existence with Path::exists or
is_dir, which use stat/lstat. On Redox these syscalls can return
errors for scheme paths even when the scheme IS registered, leading
to false 'unavailable' warnings even after the new acpid thermal/
power directories were added.

Use fs::read_dir (which already worked for actual zone enumeration)
as the existence probe instead. The match expression intentionally
calls read_dir to discard the iterator and keep only the Result;
using ? here would break the warn-once pattern.
2026-06-28 18:54:05 +03:00
vasilito dbb7bf74e9 redbear-power: clear PkgW status, remove redundant per-CPU PkgW column
- app.rs: rapl_status field shows 'n/a (run as root)' or
  'n/a (unsupported)' or 'sampling...' depending on state
- render.rs: header PkgW shows rapl_status when unavailable;
  removed per-CPU PkgW column (package power is socket-wide)
2026-06-28 18:51:09 +03:00
vasilito 03fd3a0690 redbear-power: synthetic P-state table from cpuinfo min-max, freq-based index matching
- acpi.rs: fallback creates 6 evenly-spaced P-states from
  cpuinfo_min_freq / cpuinfo_max_freq when scaling_available_frequencies
  is absent (intel_pstate, amd-pstate drivers)
- platform.rs: probe accepts cpuinfo_max_freq as valid PSS source
- app.rs: match current frequency against synthetic P-state table
  to compute current_idx without MSR access
- pss_source label: 'sysfs (cpuinfo min-max)' for intel_pstate
2026-06-28 18:32:24 +03:00
vasilito d2b969eb05 redbear-power: bulletproof cpufreq backend with writability check
- cpufreq.rs: complete rewrite — Backend enum (Redox/Linux), Cpufreq
  struct with self-discovering governor list, probe-based discovery
  on Redox, writability verification by writing current governor
  back. No hardcoded governor names — cycle order follows discovered
  list. Handles intel_pstate (no ondemand), permission-denied,
  and absent cpufreq gracefully.
- app.rs: Cpufreq stored in App instead of Governor enum, eliminates
  per-poll probe overhead. cycle_governor flashes root-required
  when sysfs denies write.
- render.rs, dbus.rs: use cpufreq.active string directly
2026-06-28 18:13:34 +03:00
vasilito ee086ded2d redbear-power: RAPL MSR constants, unit parsing, MSR-based energy reading
- msr.rs: add all Intel RAPL MSR addresses (0x606-0x64D) and AMD Zen
  equivalents (0xC0010299-0xC001029B), RaplUnit struct for unit register
  parsing with energy_to_uj/power_to_w conversion, read_rapl_energy()
  and read_rapl_energy_uj() functions
- acpi.rs: read_rapl_package_energy() now tries MSR first (Intel then
  AMD PKG energy MSRs) with unit-based µJ conversion, falls back to
  Linux powercap sysfs
- local/docs/RAPL-IMPLEMENTATION-PLAN.md: comprehensive 3-phase plan
  based on Linux 7.1 kernel analysis, Intel SDM, Fuchsia RFC-0203
  patterns. Documents P0 blocker: /scheme/sys/msr/ not implemented
  in kernel
2026-06-28 16:55:51 +03:00
vasilito 6da211161c redbear-power: sysfs freq fallback, RAPL package power, compact layout, governor verify, 0 warnings
- Freq/MHz: add read_cpu_freq_khz_sysfs() fallback when MSR unavailable
  (reads /sys/.../scaling_cur_freq + cpuinfo_cur_freq, like htop)
- PkgW: add RAPL powercap reading from /sys/class/powercap/intel-rapl
  with delta-to-watts computation (100ms min, 5s max guard)
- Governor: cycle_governor() + set_governor() now verify writes by
  reading back immediately; revert + flash error if kernel rejected
- Layout: reduce HEADER_LINES 8→7, replace 25-line controls panel with
  1-line key bar (g:%s ↑↓:cpu p/P:±pstate ... q:quit), freeing 24 rows
- Fix 3 unnecessary unsafe blocks in cpuid.rs (intrinsics now safe)
- Narrow #![allow] from 5 broad attrs to dead_code only + targeted
  #[allow(unreachable_patterns)] on key-dispatch match
- 0 warnings (was 53)
2026-06-28 16:29:18 +03:00
vasilito 909cce0f5d cpufreqd, redbear-power: read CPU count from /scheme/sys/cpu
On Redox the kernel's sys:cpu scheme is a single file (kernel/src/scheme/
sys/cpu.rs) whose contents start with 'CPUs: N\n', not a per-CPU directory.
The kernel does not create /dev/cpu/ at all, so the prior read_dir-based
enumeration always fell through to the single-CPU fallback on Redox —
hiding the fact that the kernel had successfully brought up the APs and
reporting only CPU 0 to the governor / power TUI.

Read /scheme/sys/cpu and parse the 'CPUs:' line first; fall back to
/dev/cpu/ for Linux hosts.
2026-06-28 16:26:30 +03:00
vasilito 8af119d1a9 Remove duplicate redbear-netctl-console recipe (nested inside redbear-netctl) 2026-06-28 00:01:47 +03:00
vasilito e4362bbfad redbear-power: surface tab keys in --help and Controls panel
The tab keys (1-9 jump directly, T cycles next) were
always wired in main.rs but not documented anywhere
visible. Operators could not discover how to switch
between the 9 data tabs (Per-CPU / System / Info /
Motherboard / Battery / Sensors / Network / Storage
/ Process) without reading the source.

Two surfaces now surface the tab keys as the FIRST
entries, ahead of governor / P-state / throttle
controls:

1. Controls panel (visible at the bottom of every
   TUI frame): adds a 'Tabs:' line listing the 9
   direct-jump keys with abbreviated tab names, plus
   a [T] cycle line. Implemented in
   render_controls().

2. --help output (HELP_TEXT constant, shown via
   'redbear-power --help' and the in-TUI [?] help
   overlay): adds a TABS: section at the top of the
   INTERACTIVE CONTROLS block, listing all 9 tabs in
   3-row grid + the [T] cycle key.

The keys themselves are unchanged: '1'-'9' for
direct tab jump, 'T' for cycle. No new keybindings
or behaviors added.

Discovered during interactive smoke-testing on the
Linux host: the binary worked perfectly but tab
navigation was a discoverability hole. Fix-only,
no semantic change.

186/186 tests still pass (the controls panel
render is covered by the existing snapshot tests).
2026-06-21 15:57:31 +03:00
vasilito 1a9d77761e fix(qtdeclarative): properly generate qtquick-config.h features
The qtquick-config.h file was being generated empty by the cmake
build, causing 'division by zero in #if' errors in downstream
consumers (SDDM, KWin) because QT_CONFIG(quick_shadereffect) and
QT_CONFIG(quick_draganddrop) were undefined.

Two fixes:
1. Explicitly enable QT_FEATURE_quick_shadereffect and
   QT_FEATURE_quick_draganddrop in the cmake configuration.
2. Add a safety net that regenerates qtquick-config.h with the
   required feature definitions if cmake produces an empty file.
2026-06-21 15:12:40 +03:00
vasilito f83a059c25 redbear-power: v1.43 history reclaim LRU
The first item from the v1.42 deferred list: a
configurable LRU cap on the per-PID history maps.
On long uptimes with thousands of short-lived procs
(build servers, CI runners), the maps would grow
without bound, eventually consuming significant
memory. v1.43 caps the maps at 500 PIDs by default
and evicts the LRU entry on overflow.

The cap
  - App::max_history_pids: usize (default 500)
  - 0 = disable (reaper still prunes exited PIDs)
  - Shared across the 3 per-PID maps (io_history,
    cpu_history, rss_history). They are always
    populated in lockstep so a per-PID CPU history
    without the corresponding IO history would be
    a 'ghost' entry that confuses the renderer.
  - disk_history is NOT capped (keyed by disk name,
    natural bound on block device count).

LRU tagging
  - New App::pid_last_seen: BTreeMap<u32, u64>
  - New App::refresh_tick: u64 (incremented on every
    update_io_history call)
  - We use a refresh counter, not Frame::count(),
    because the history update happens during
    refresh (not during render). Frame::count would
    tag currently-visible PIDs rather than
    recently-updated PIDs — a different (and
    incorrect) notion.

Eviction algorithm
  1. Increment refresh_tick
  2. Reap exited PIDs from all 3 maps and
     pid_last_seen
  3. If pid_last_seen.len() > cap: sort by tick
     ascending, take the first overflow entries,
     remove from all 3 maps + pid_last_seen
  4. Continue with the existing pipeline

Cost: O(n log n) per refresh, n bounded by 500.
At 500 PIDs: ~4500 comparisons per refresh,
<100µs. Memory budget: ~28 KB at cap, vs
unbounded growth without the cap (~5.5 MB at
100k PIDs).

Tests
  - 3 new tests (eviction removes oldest, cap=0
    disables, no-op under cap).
  - 186/186 tests pass (was 183 in v1.42).

The improvement plan doc is also updated with §67
covering the v1.43 architecture, the cap policy,
the LRU tagging, the eviction algorithm, the
memory budget, and the v1.44 deferred list.
2026-06-21 14:00:02 +03:00
vasilito 0771fa2ff6 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.
2026-06-21 13:38:24 +03:00
vasilito 34e9ec2a05 redbear-power: v1.41 per-thread IO aggregation
The next item from the v1.40 deferred list: walk
/proc/<pid>/task/*/io for every process and sum
read_bytes and write_bytes across all TIDs. Surfaces
'is one thread of this 32-thread process hammering
disk?' which the process total hides.

Linux kernel attribution quirk
  /proc/<pid>/io:read_bytes is the process total,
  NOT the per-thread sum — the kernel attributes all
  IO to the process even when threads initiate it.
  So the two surfaces can match, diverge, or be
  independently None depending on kernel version and
  permission model. We never compare or subtract them.

New fields on ProcessInfo
  - thread_io_read_kb, thread_io_write_kb (summed bytes)
  - thread_io_read_rate_kbs, thread_io_write_rate_kbs
    (delta-based rate via the same compute_rate_kbs
    helper used for the process-total rates)

New sort modes
  - ThreadIo: read+write total
  - ThreadIoR: read only
  - ThreadIoW: write only
  The cycle reaches all 3 via a back-door arm of next()
  that returns to Rss (not Name) to avoid breaking the
  main loop.

New column in Process panel
  T-IO between the per-thread rate and the MEM column.
  Now 12 columns total. PID detail popup gets a
  [thread_io] section that re-reads the task dir on
  open for a current value.

Tests
  - 6 new tests (3 in process.rs for read_thread_io,
    3 for the sort modes + cycle).
  - 170/170 tests pass (was 164 in v1.40).

The improvement plan doc is also updated with §65
covering the v1.41 architecture, the Linux kernel
attribution quirk, the new fields, the sort cycle
back-door, the cost analysis (~500 reads/sec at
typical desktop loads, ~7500 at 128-thread server
loads — well within budget), and the v1.42
deferred list.
2026-06-21 13:19:20 +03:00
vasilito 2f8e35a88a redbear-power: v1.40 persistent session state
The first item from the v1.39 deferred list: the user's
tab, sort mode, sort direction, tree mode, filter, and
fold set now survive a restart of redbear-power.

Architecture
  - New module session.rs (separate from config.rs which
    is read-only system-wide config).
  - config.rs: behavior config (refresh interval, theme,
    keybindings) — read once at startup, never written.
  - session.rs: mutable per-user runtime state — read at
    startup AND written on every tab change and on quit.

Storage
  - $XDG_CONFIG_HOME/redbear-power/session.toml (preferred)
  - ~/.config/redbear-power/session.toml (fallback)
  - Writes are atomic: temp file + rename(). A crash
    between write and rename leaves the prior session
    intact.

Save hooks
  - Every set_tab() call: tab is the high-signal event
    the user explicitly opted into.
  - On graceful quit (q/Esc): captures the final sort,
    filter, and fold set.

Failure modes
  - load() never errors. Missing file = defaults.
    Corrupt file = defaults + one-line warning.
  - save() never errors. Permission denied = eprintln!
    warning. The next launch reads the prior session
    (or defaults) and starts from there.

Tests
  - 6 new tests in session.rs (round-trip, missing-file,
    malformed-toml, atomic-save, default-state, end-to-end
    via App::save_session()).
  - 164/164 tests pass (was 158 in v1.39).

The improvement plan doc is also updated with §64
covering the v1.40 architecture, storage paths, save
policy, failure modes, and the v1.41 deferred list.
2026-06-21 12:18:13 +03:00
vasilito 5bd371c070 redbear-power: v1.39 cursor preservation + T-IO column + process environ
v1.39 lands three htop/btop parity features plus the
audit-fix discipline introduced in v1.37/v1.38:

1. Cursor preservation across sort
   o and i keypresses no longer reset the cursor to row 0.
   The cursor follows the currently-selected PID through the
   re-sort. Implemented as App::remember_and_restore_cursor()
   which walks the post-filter visible list. Three regression
   tests: follows PID, respects filter, falls back when PID
   exits.

2. Per-thread IO rate (T-IO column)
   New ProcessInfo::io_per_thread_rate_kbs() returns the
   aggregate IO rate divided by num_threads, surfacing
   thread-pool pressure that's hidden in the aggregate. A
   32-thread process at 320 KiB/s is the same as a 1-thread
   process at 10 KiB/s in aggregate, but very different
   in operator-relevant 'IO per worker' terms. Returns None
   when num_threads <= 0 (data error, not '0 KiB/s per
   thread' which would mislead the operator). Three unit
   tests cover the divide, the missing-total case, and the
   zero-threads case.

3. Process environ in PID detail
   /proc/<pid>/environ read as NUL-separated KEY=VALUE
   pairs, sorted by key for stable popup rendering.
   Rendered as the first 8 vars in the PID detail popup
   with a '(N variables)' header. htop F7 parity. Three
   unit tests: parse self environ, missing PID, value
   containing '=' (must split on FIRST '=' only).

The improvement plan doc is also updated with sections
56-63 covering v1.32 (sparklines) through v1.39
(per-thread IO + environ) since the doc previously
stopped at v1.31.

Test count: 158/158 pass (was 149 in v1.38.1).
2026-06-21 12:03:12 +03:00
vasilito 01de65bd03 redbear-power: v1.38.1 fix io_priority field index
v1.38 review found a HIGH severity bug in the v1.38 io_priority reader: it read fields[44] (overall field 47) which on modern Linux kernels (~52 fields total) is a memory address (~9x10^13) that overflows u32 and silently returns None for every process. The PID detail popup shows 'IO priority: ?' on every process, on every Linux kernel released in the last ~5 years.

Fix: change fields[44] to fields[15] (post-comm field 15 = overall field 18 = priority per the /proc/[pid]/stat layout). The doc comment now spells out the per-field index map 0-15 so a future maintainer can verify the indexing.

Strengthened the regression test: the v1.38 test did 'let _ = ...' which passed even when the reader was broken. The new test reads /proc/self/stat directly (bypassing the function), parses the priority field at vec[15] itself, and asserts the function returns the same value. A 'sanity' assertion (value < 1_000_000_000) catches the 'reading a memory address' failure mode specifically — a value > 10^9 means we're reading the wrong field again.

This is exactly the kind of bug the v1.37 audit warned about: 'the existing 4 tests that exist for update_io_history all assert the post-normalize storage value, not what the renderer actually draws. A simple integration test that renders and reads back would have caught it.' The v1.38 test for io_priority had the same shape — 'let _ = ...' passes silently. The v1.38.1 test now reads the actual value back.

Test count 149 -> 149 (strengthened one test in place; no new test count).

Redox stripped binary: 4,348,776 bytes (unchanged; the field index is 1 character).
2026-06-21 11:00:32 +03:00
vasilito 9cd0a25906 redbear-power: v1.38 audit fixes + htop/btop parity
v1.37 audit found 2 new bugs + recommended 5 v1.38 htop/btop-parity features. This release fixes both bugs and ships all 5 features.

v1.37-0 (HIGH): set_tab() clears last_clicked_cpu

The v1.37 re-click-to-expand feature set last_clicked_cpu on click but never reset it on tab switch. A user who clicked Per-CPU row 5, switched tabs, and came back would unexpectedly toggle expand. Fix: add App::set_tab(TabId) helper that resets both last_clicked_cpu and expanded_cpu, and route all 9 tab keys (1-9) + T through it.

v1.37-1 (MEDIUM): mouse click respects filter

The Process tab mouse click set process_cursor from the raw screen row, ignoring the active filter. With a filter active, the cursor highlight wouldn't align with the click, and right-click opened the wrong PID detail. Fix: new App::process_cursor_at_y(y, first_data_y) that walks the post-filter visible list and clamps to the last visible row. Wired into both left-click and right-click in handle_mouse.

v1.38-2: SortDir + i key for direction toggle

htop parity for the 'i' key. New App.sort_ascending: bool. The SortMode enum gets a new sort_ascending(procs, true) method (the existing sort() now delegates to sort_ascending(procs, false) for backward compat). On each refresh, if sort_ascending is true, the processes are re-sorted after the default descending pass. Press 'i' to flip; the status flash includes the current direction.

v1.38-3: cmdline + io_priority in PID detail

htop parity. New PidDetail.cmdline reads /proc/[pid]/cmdline, replaces NUL with space, strips trailing NULs. Rendered in the PID detail popup (truncated to 120 chars). New PidDetail.io_priority reads /proc/[pid]/stat field 47. Both are tolerant of missing files.

v1.38-4: per-disk I/O throughput sparkline

btop parity. New App.disk_history: BTreeMap<String, VecDeque<u8>> keyed by disk name. Mirrors the io_history pattern: each storage refresh collects raw kbps samples, normalizes per-disk against its own max, writes u8 to the public history. Rendered in the Storage tab as a 12-char sparkline next to each disk name. Reaps disks that have disappeared.

Test count 140 -> 149 (+9):
- set_tab_clears_last_clicked_cpu_and_expanded_cpu
- process_cursor_at_y_respects_filter
- process_cursor_at_y_clamps_to_last_visible
- sort_ascending_flips_rss_order
- read_cmdline_replaces_nul_with_space
- read_cmdline_handles_missing_pid
- read_io_priority_handles_self
- read_io_priority_handles_missing_pid
- update_disk_history_reaps_exited_disks

Redox stripped binary: 4,348,776 bytes (+106 KiB from v1.37).
Compile warnings: 56 (unchanged; all pre-existing).
2026-06-21 09:50:31 +03:00
vasilito fe4087d471 redbear-power: v1.37 audit fixes (Bug 1-4 + P1 parity)
v1.32-v1.36 audit (shipped 5 features in one batch) had 4
real bugs the test suite missed, plus 2 htop/btop-parity
gaps. This release fixes all 4 bugs and adds the 2 parity
features, with regression tests for each.

P0-1 (CRITICAL) Bug 1: Sparkline type confusion

The v1.32 algorithm stored raw f64 bits in u64 cells, then
normalize_history overwrote them with integer-as-u64
(0..=255 cast to u64). The renderer's
f64::from_bits() interpreted these as f64 bits, producing
subnormal ~6e-324, then .max(0.0).min(255.0) as u8 -> 0.
Every sparkline cell rendered as blank after the first
refresh. The v1.31 IO-RATE sparkline worked ONCE then
degraded; the v1.32 CPU% and RSS sparklines never worked.

Fix: change the three history maps from VecDeque<u64> to
VecDeque<u8>. update_io_history now uses a two-phase
algorithm: (1) collect raw f64 samples into per-PID
pending Vecs, (2) write normalized u8 to the public
history. The renderer reads u8 directly with no f64
round-trip. Side effect: the per-refresh overwrite
replaces the v1.31 sliding window. 12 samples of
in-flight history is the same time window in practice
(12 * 6.5s = 78s) as 12 refreshes of windowed history
under the same refresh cadence.

P0-2 Bug 1 regression test: tests assert the io_history
cells are non-zero after update with a non-zero rate.
v1.32 would have left them at 0 (subnormal); v1.37
produces true u8 1..=255.

P0-3 (HIGH) Bug 2: Tree prefix bar continuation

The v1.34 ancestor_active_in_next check walked the next
row's chain only 0..=d steps. For a tree 1->{2->3, 4}
at row pid=2, the bar at depth 0 (above pid=2) checked
only one step of the next row's chain and missed
ancestor=1 in the chain. Result: bar showed '  ' instead
of '| ' for trees deeper than 1. v1.37 walks the full
chain to the root.

P0-4 Bug 2 regression test: 8 tree_prefix tests covering
single root, two-level fan-out, three-level chain,
sibling-after-deep, fold marker on/off, and the leaf
case. None existed before; would have caught Bug 2.

P0-5 (MEDIUM) Bug 3: Mouse y-offset

The Process tab render layout is: line 0 = title,
line 1 = blank, line 2 = column header, lines 3+ = rows.
The mouse handler subtracted table.y + 2 (one too high).
Clicking the column header mapped to process_cursor = 0
(first data row), and every row was off by one. v1.37:
table.y + 3.

P0-6 (LOW) Bug 4: Right-click filter

Re-verified: selected_pid() already uses
visible_processes() (post-filter list), so the audit's
bug report was incorrect. Added a regression test
instead that asserts selected_pid returns the right PID
with an active filter.

P1-1 htop parity: Re-click to expand

Mirrors htop's KEY_RECLICK handling. A second left-click
on the same CPU row toggles expand; a click on a different
row just selects. New App::last_clicked_cpu: Option<u32>
tracks the last click.

P1-2 htop parity: PageUp/PageDown

Already wired in v1.35 via page_selection(tab-aware).
Added a regression test that asserts the Process-tab
PageDown moves the cursor by ROWS_PER_PAGE (8) rows and
clamps at the last visible row.

Test count 127 -> 140 (+13):
- 2 new update_io_history tests (Bug 1 regression)
- 1 new io_history no-rate test
- 8 new tree_prefix tests (Bug 2 regression)
- 1 new selected_pid filter test (Bug 4 regression)
- 1 new page_selection test (P1-2)

Redox stripped binary: 4,242,280 bytes (-8 KiB from v1.36;
the new methods' bodies are tiny and the linker dedup'd
shared code).
Compile warnings: 56 (unchanged; pre-existing).
2026-06-21 09:17:24 +03:00
vasilito 0eea77541a fix(openssl3): add missing core.h headers to fix build
OpenSSL 3.5.3 tarball is missing include/openssl/core.h and
include/internal/core.h headers. These are standard OpenSSL 3.x
headers that are referenced by multiple source files.

- Add P0-add-missing-core-headers.patch with both missing headers
- Update recipe.toml to apply the patch
- Headers sourced from official OpenSSL 3.5.3 branch on GitHub

Fixes build failure:
  fatal error: openssl/core.h: No such file or directory
2026-06-21 09:07:27 +03:00
vasilito 7b973f932b redbear-power: v1.36 mouse click to position Process cursor
Extends the existing mouse handler (which already supports
Per-CPU wheel/left/right/middle clicks) to also work on the
Process tab.

- Wheel up/down on the Process tab: cursor moves 1 row
  (process_cursor is set via the existing move_selection
  dispatcher, which is already tab-aware)
- Left click on a row: process_cursor jumps to that row.
  The Process tab has 2 lines of header (title + blank) and
  1 line of column header, so rows start at body.y + 2.
  Click in the body's empty space (below the rows) is a no-op
  (saturating_sub clamps to 0).
- Right click on a row: positions the cursor AND opens the
  PID detail popup (right-click is the natural 'inspect' gesture).
- The body rect for the Process tab is the same as 'table'
  in the existing handler since the Process tab uses the
  full body area (not split into header + table like Per-CPU).

Test count 127 (unchanged; mouse handling is TUI-time and
hard to unit-test without a full terminal harness).

Redox stripped binary: 4,250,472 bytes (+12 KiB from v1.35;
the new branches are inline in the existing handler).
Compile warnings: 56 (unchanged).
2026-06-21 07:37:08 +03:00
vasilito a5237091fc redbear-power: v1.35 Home/End + g/G keypresses
Adds cursor jump-to-edge keypresses for the Process tab and
extends Home/End to also work on the Per-CPU tab.

- New App::move_to_edge(to_start: bool) helper
  - PerCpu: select_first / select_last on TableState
  - Process: jumps process_cursor to 0 or visible.len()-1
  - Other tabs: no-op
- New keypresses: Home, End, g, G
  - Home and g: jump to first row
  - End and G: jump to last row
- g/G are vim-style aliases for Home/End (some users
  reach for these first)

Test count 125 -> 127 (+2):
- move_to_edge_process_jumps_to_first_and_last
- move_to_edge_process_handles_empty

Redox stripped binary: 4,238,184 bytes (+12 KiB from v1.34).
Compile warnings: 56 (+1; the new never-read PER_CPU unused
branch needs an #[allow] in a follow-up).
2026-06-21 07:34:49 +03:00
vasilito 18c3f2ad32 build: cleanup script to free 58GB of build artifacts
- Remove recipe target directories (47GB)
- Clean build directory artifacts
- Remove old temp logs
- Clean Cargo target directories
- Free disk space from 32GB to 91GB available
2026-06-21 07:31:31 +03:00
vasilito c1044da3b7 redbear-power: v1.33 SortMode::RChar / WChar (VFS-level IO)
Adds two new sort modes that target the VFS-level byte counts
exposed by /proc/[pid]/io:rchar and wchar. These differ from
the existing read_bytes/write_bytes:

  read_bytes: storage-level bytes that hit the disk
  rchar:      VFS-level bytes (includes cache hits, tty output)

Useful for the 'is this proc doing lots of syscalls?' question
that read_bytes misses (cache-served reads count toward rchar
but not read_bytes).

- New fields on ProcessInfo: io_rchar_kb: u64, io_wchar_kb: u64
  (u64, not Option: rchar/wchar default to 0 if absent on
  very old kernels, never sentinel-needed)
- read_io_file() now returns a 4-tuple
  (read_bytes, write_bytes, rchar, wchar) instead of 2-tuple
- New SortMode variants RChar and WChar
  - cycle: Rss -> Cpu -> Io -> ... -> IoWriteRate -> RChar
    -> WChar -> VSize -> Pid -> Name -> Rss
  - name(): 'RChr' and 'WChr'
  - sort(): descending by io_rchar_kb / io_wchar_kb
- Column-swap: when sort is RChar, MEM column shows RChr value;
  when WChar, shows WChr. Default and other modes use RSS.

Test count 123 -> 125 (+2):
- sort_by_rchar_descending (VFS reads, pid 2 with 5000 first)
- sort_by_wchar_descending (VFS writes, pid 1 with 999_999 first)
- sort_cycle and io_name_is_io updated for RChar/WChar

Redox stripped binary: 4,225,896 bytes (+12 KiB from v1.32).
Compile warnings: 55 (unchanged).
2026-06-21 07:29:56 +03:00
vasilito bdec5061ef redbear-power: v1.32 CPU% and RSS per-PID history (3 sparklines)
Generalizes the v1.31 io_history pattern to two more metrics:
cpu_history and rss_history. Each Process tab row now shows
three sparklines:
  IO-RATE  (12 samples, full 78s of IO rate history)
  CPU%    (6 samples, last 39s of CPU usage)
  RSS     (6 samples, last 39s of memory)

The two new sparklines are 6 chars wide (vs 12 for IO-RATE) to
keep the panel within a 120-col terminal.

Implementation:
- Two new BTreeMap<u32, VecDeque<u64>> fields on App
- update_io_history() now updates all three maps in a single
  3-pass sweep (reap, append, normalize) for all metrics
- Extracted private helpers push_sample() and normalize_history()
  for the per-metric work; both operate on the map type
  generically
- New render::sparkline_short() helper: renders the last
  'width' samples of a history, padding with leading spaces
  for short histories

Test count 121 -> 123 (+2):
- update_io_history_populates_cpu_and_rss_for_each_pid
  (every PID gets cpu/rss entries, not just PIDs with non-zero
  values)
- update_io_history_reaps_all_three_maps (phantom-PID reap
  spans all three maps)

Redox stripped binary: 4,213,608 bytes (+12 KiB from v1.31).
Compile warnings: 55 (unchanged).
2026-06-21 07:23:27 +03:00
vasilito 2597246908 redbear-power: v1.31 per-PID IO rate sparkline
Activates the v1.25-deferred 'persistent rate sparkline' future-use.
Each process in the Process tab now shows a 12-sample sparkline
of its IO rate history (last 78 seconds at the 6.5s process
refresh cadence).

- New App.io_history: BTreeMap<u32, VecDeque<u64>>
  Per-PID history of raw f64-bit rate samples. BTreeMap for
  stable iteration; VecDeque for O(1) push-back + pop-front.
- PROCESS_IO_HISTORY_LEN = 12 (12 samples * 6.5s = 78s of history)
- App::update_io_history() runs after sort_tree + apply_fold
  on every process refresh. Three-pass algorithm:
    1. Reap: drop history for PIDs that exited
    2. Append: push new f64-bit sample for PIDs with known rate
       (PIDs with None rate are skipped, no entry created)
    3. Normalize: divide each sample by the per-history max,
       scale to u8 0..=255. Separate pass so max is computed
       once per history, not per sample.
- render::io_rate_sparkline(&[u8]) helper maps 0..=255 to
  Unicode chars (\u2581\u2582... matches existing load sparkline)
- New 'IO-RATE' column in Process panel between RSS/VSZ and
  COMM. 12 chars wide. Empty spaces for PIDs with no history
  yet (first tick after startup).
- Why u64 storage of f64 bits: normalization needs the full
  f64 range; clamping to u8 before normalize would lose
  precision for high-rate PIDs.

Test count 117 -> 121 (+4):
- update_io_history_reaps_exited_pids
- update_io_history_normalizes_against_max (100/200*255=127.5
  rounds to 128; 200/200*255=255)
- update_io_history_handles_all_zero (no div by zero)
- update_io_history_skips_pids_without_rate (None rate \u2192
  no entry created; no panic)

Redox stripped binary: 4,201,320 bytes (+4 KiB from v1.30).
Memory: ~91 KiB for 600 PIDs (negligible).
Compile warnings: 55 (unchanged).

Notes:
- CPU% sparkline per process: defer (same pattern, separate work)
- RSS sparkline per process: defer
- Variable sparkline length: defer (header is 'IO-RATE' not
  'IO-RATE 12' so a future change to PROCESS_IO_HISTORY_LEN
  doesn't need a header update)
- Per-PID scaling (not global): each PID's max is 255. A
  long-running PID at 5 KiB/s steady shows full bars; a
  bursty PID that just started at 50 KiB/s also shows full
  bars. Global scaling would flatten the long-running one.

Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA755
2026-06-21 07:19:16 +03:00
vasilito e2570104a5 redbear-power: v1.30 Process-tab cursor navigation
Activates the v1.29-deferred cursor-navigation future-use. The
Process tab is now fully interactive: j/k and arrow keys move
the cursor, PageUp/PageDown scroll by 8 rows, the visible row
is bolded as the cursor, Enter and Space operate on the
cursor's PID.

- move_selection is now tab-aware:
    PerCpu -> move_cpu_selection (old behavior)
    Process -> move_process_selection (new)
    other -> no-op
- move_process_selection clamps process_cursor to
  [0, visible.len()-1] (saturating_add for big deltas)
- page_selection is also tab-aware (8 rows per page for Process)
- j/k hotkeys (vim-style) call move_selection
- visible_processes() helper extracted (deduplicates the
  filter+collect logic that move_process_selection and
  selected_pid both needed)
- selected_pid() now uses process_cursor directly (was using
  table_state.selected() which was the Per-CPU widget's
  selection — wrong tab indirection)
- theme::CURSOR: bold style for the cursor row (no background
  color; background flickers on rapid style changes on some
  terminals; bold is stable)
- Render layer applies theme::CURSOR when current_tab==Process
  AND the visible_index matches process_cursor AND focused

Test count 111 -> 117 (+6 in new app::tests mod):
- move_process_selection_down_clamps_to_last
- move_process_selection_up_clamps_to_zero
- move_process_selection_empty_list_is_noop
- move_process_selection_respects_filter
- selected_pid_returns_none_when_empty
- selected_pid_returns_none_when_filter_excludes

make_app_with_processes(n) helper clears App::new()'s /proc
read first so the test fixtures don't mix with real procs.

Redox stripped binary: 4,197,224 bytes (+16 KiB from v1.29).
Compile warnings: 55 (unchanged).

Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA754
2026-06-21 07:02:11 +03:00
vasilito 988e8b29bb redbear-power: v1.29 fold/expand tree
Activates the v1.27-deferred fold/expand feature. The tree
view from v1.27 is now interactive: pressing Space on a
parent row toggles whether its descendants are visible.

- New App.folded: BTreeSet<u32> (PIDs whose subtrees are
  collapsed; stable iteration order for future debug dumps)
- New App.process_cursor: usize (Process-tab cursor; distinct
  from table_state which tracks the Per-CPU tab)
- New process::apply_fold(processes, folded) -> Vec<ProcessInfo>
  Hides descendants of any PID in . The fold target
  itself stays visible. Roots are never hidden. Cycles
  tolerated (sort_tree's visited set prevents infinite loops).
- Fold indicator in tree_prefix: \u25b6 for folded, \u25bc for
  expanded, no marker for leaf rows
- Space keypress (in tree mode only) toggles fold on the
  cursor's selected PID; flashes 'folded PID N' or
  'unfolded PID N' (or 'has no children to fold' for leaves)
- sort_tree kept pure; apply_fold is a separate post-step
  applied in app.rs after sort_tree

Test count 107 -> 111 (+4):
- apply_fold_empty_set_is_identity
- apply_fold_hides_descendants_of_folded_root (folds root)
- apply_fold_hides_subtree_of_folded_child (folds middle;
  sibling of folded node stays visible)
- apply_fold_unfold_restores (toggle off)

Redox stripped binary: 4,180,840 bytes (-8 KiB from v1.28;
linker dedup'd some shared code).
Compile warnings: 55 (unchanged).

Notes:
- No cursor navigation yet (j/k, down/up). Default cursor
  is row 0, so user can fold the first process but cannot
  yet move down. Defer to v1.30.
- No persist of fold state across redbear-power restarts.
  Would require a config file. Defer.

Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA753
2026-06-21 01:55:48 +03:00
vasilito df3021575e redbear-power: v1.28 virtual size sort (activates vsize_kb)
Closes the v1.23-deferred 'vsize_kb' future-use. Adds SortMode::VSize
that sorts by virtual size, and swaps the Process panel's MEM
column to show VSZ (instead of RSS) when that sort is active.

The column-swap pattern (the column being sorted IS the column
being shown) keeps the panel at 10 columns instead of growing
to 11. htop uses the same pattern: when you sort by a field,
that field's column expands to show the data. No new column
means no terminal-width pressure at 1280x720 framebuffer.

The 'ppid' field's #[allow(dead_code)] is also removed (now
actively read by sort_tree + tree_prefix from v1.27). Both
fields now have proper doc comments explaining their use
(vs the v1.23 'reserved for future use' placeholder).

VSZ is a virtual address-space metric (mmap'd libraries, heap,
stack, reserved-but-uncommitted) and is often much larger than
RSS. Useful for 'who is using the most address space' but NOT
for 'who is using the most physical memory' (use RSS for that).
This caveat is now documented in the field's doc comment to
prevent operator confusion.

Test count 105 -> 107 (+2):
- sort_by_vsize_descending (basic)
- sort_by_vsize_uses_vsize_not_rss (contract test: huge VSZ +
  tiny RSS sorts above tiny VSZ + huge RSS; catches any
  'optimization' that uses the larger of the two fields)
- sort_cycle and io_name_is_io updated for VSize

Redox stripped binary: 4,189,032 bytes (+4 KiB from v1.27).
Compile warnings: 55 (no net change; the 2 removed
#[allow(dead_code)] annotations cancel against 2 new
warnings that did not exist before because the fields were
only accessed from the parse path).

Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA752
2026-06-21 01:46:11 +03:00
vasilito 3dcdb758e7 redbear-power: v1.27 process tree view
Activates the v1.23-deferred 'ppid' future-use: parents render
above their children with ASCII tree connectors. The default
view is flat (no behavior change); the 'T' hotkey toggles
tree mode and flashes the status line.

Algorithm (in process::sort_tree):
1. Build pid -> index map
2. Group children by ppid (ppid -> Vec<index>)
3. Roots = ppid==0 or ppid not in pid set
4. Sort each sibling group by current SortMode (so e.g. RSS
   sort still shows top-RSS child first within a parent)
5. DFS from each root, emitting parent + descendants pre-order
6. Defensive: append unvisited procs at end (cycle fallback)

Cycle protection: visited set; revisiting a PID stops recursion
(its children are still emitted once).

Render: tree_prefix(pid, ppid, all) returns
  ''  (root)
  '  \u2514\u2500 ' (last child)
  '  \u251c\u2500 ' (non-last child)
Walks ppid chain to compute depth (max 64 hops).

Status line: 'view: tree' shown when on; help text mentions 'T'.

Test count 101 -> 105 (+4):
- sort_tree_emits_parents_before_children (4-proc tree)
- sort_tree_handles_orphans (ppid not in list)
- sort_tree_handles_cycles (1->2->1 cycle)
- sort_tree_empty_input

Redox stripped binary: 4,184,936 bytes (+16 KiB from v1.26).
Compile warnings: 55 (unchanged).

Notes:
- vsize_kb still has #[allow(dead_code)]; will be activated in
  a future memory-detail panel release.
- Tree is static (no fold/expand); defer to a v1.28 if needed.
- ppid's #[allow(dead_code)] can be removed in a follow-up
  (now actively read by sort_tree and tree_prefix).

Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA751
2026-06-21 01:39:29 +03:00
vasilito 04894a18c9 redbear-power: v1.26 remove dead code (BREAKING)
Completes the v1.22 audit W2 cleanup. v1.23 deferred this for a
CHANGELOG note; this release documents the breaking change.

REMOVED (no callers anywhere in the source tree):

- ProcInfo::read_with_cpu_pct(prev, dt_secs, num_cpus)
  Was a 1-line wrapper around read_with_cpu_pct_sorted(..., Rss)
  that no caller actually used. Migration: call
  read_with_cpu_pct_sorted(prev, dt, ncpu, SortMode::default())
  inline (or just use ProcInfo::read() if RSS is fine).

- ProcInfo::available() -> bool
  Was a pre-flight check ('is /proc mounted?') that no caller
  used. read() already returns ProcInfo::default() when /proc
  is absent, so the empty result is the same signal. Migration:
  check !proc.is_empty() after a read, or call read() and
  handle the empty case.

OTHER CHANGES:

- Removed unused 'use std::path::Path;' (was only used by the
  removed ProcInfo::available).
- Updated read_with_cpu_pct_sorted doc comment to mention
  'CPU% and IO rates' (reflects the v1.25 addition).

BREAKING: any external consumer of redbear-power's process module
that called either of these methods will fail to compile. The
recipe's own source (the only known consumer) is updated.

Test count: 101 (unchanged; removed methods were untested).
Compile warnings: 55 -> 54 (the unused Path import is gone).

Redox stripped binary: 4,168,552 bytes (unchanged; the removed
code was tiny and the linker dedup'd the wrapper body).

Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA750
2026-06-21 01:05:09 +03:00
vasilito aaa1b950b4 redbear-power: v1.25 IO rate column + rate sort modes
Per-process IO is now also a throughput metric (KiB/s), not just
cumulative. Cumulative bytes favor long-lived processes regardless
of activity; rate is what operators actually want for 'what is
hammering the disk right now'.

- New fields on ProcessInfo: io_read_rate_kbs, io_write_rate_kbs
  (Option<f64>; None when prev missing, current None, or dt<=0)
- New method: io_total_rate_kbs() (sum; same None semantics)
- New helper: compute_rate_kbs(prev, now, dt) -> Option<f64>
  uses saturating_sub for clock-reset safety
- read_with_cpu_pct_sorted now also computes the two rate fields
  (negligible cost: 2 subs + 2 divs per process per refresh)
- New SortMode variants: IoRate, IoReadRate, IoWriteRate
  inserted in cycle after IoWrite
- name() returns 'IO/s', 'R/s', 'W/s' for status line
- New sort_by_io_rate_field() helper (Option<f64> partial_cmp)
- New format_rate_kbs() on ProcessInfo (KiB/s, MiB/s, GiB/s, TiB/s;
  saturates negative to 0)
- New RATE column in the Process panel between IO and RSS

Test count 87 -> 101 (+14):
- 6 compute_rate_kbs edge cases (basic, None prev/now, dt<=0,
  saturating underflow, idle = zero)
- 2 io_total_rate_kbs (sum, None)
- 2 sort-by-rate (total, read-pushes-missing)
- 4 format_rate_kbs (sub-KiB, 1 MiB, 1 GiB, negative)
- sort_cycle and io_name_is_io updated for new variants

Redox stripped binary: 4,168,552 bytes (+49 KiB from v1.24;
14 new tests + 2 sort modes + 2 fields + render column + 3 helpers).
Compile warnings: 55 (unchanged).

Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA749
2026-06-21 00:50:30 +03:00
vasilito 6f99194049 redbear-power: v1.24 split IO sort (IO-R, IO-W)
htop has had separate read/write sort modes since 2.0; v1.22
conflated them via io_total_kb(). v1.24 splits SortMode::Io
into three variants so operators can find read-heavy (DB
servers) vs write-heavy (log shippers) processes.

- New variants: SortMode::IoRead, SortMode::IoWrite
- Cycle updated: Rss -> Cpu -> Io -> IoRead -> IoWrite -> Pid
  -> Name -> Rss
- name() returns 'IO', 'IO-R', 'IO-W' for disambiguation
  (shown in status flash on 'o' keypress)
- Extracted sort_by_io_field() helper: shared 4-arm comparator
  for (Some, Some) descending, (Some, None) Less,
  (None, Some) Greater, (None, None) Equal. Eliminates the
  DRY violation of repeating the 4-arm match in three places.
- Sentinel semantics preserved: None still sorts below Some;
  column still renders em-dash for unreadable /proc/[pid]/io
- Column header unchanged: 'IO' column shows per-process
  total; sort direction is in the status line. Minimal change;
  adding separate R/W columns would push the panel past 100
  chars and lose comm truncation.

Test count 83 -> 87:
- sort_by_io_read_ignores_writes
- sort_by_io_write_ignores_reads
- sort_by_io_read_pushes_missing_to_bottom
- sort_by_io_write_pushes_missing_to_bottom
- io_name_is_io now also locks IO-R and IO-W strings
- sort_cycle and sort_cycle_includes_io updated for new cycle

Redox stripped binary: 4,119,400 bytes (-8 KiB from v1.23;
the helper dedup actually shrunk the binary).
Compile warnings: 55 (unchanged; all new variants are used).

Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA748
2026-06-21 00:41:30 +03:00
vasilito 6dd2ddafd9 redbear-power: v1.23 IO sentinel + single-pass parse
The v1.22 audit + htop cross-reference surfaced a real defect:
on Redox, daemons whose /proc/[pid]/io is not exposed (permission
denied, proc scheme gap) silently clustered at 0 B in the IO
column, indistinguishable from genuinely idle processes. This
made SortMode::Io unreliable for finding the real IO hogs.

This release promotes the IO column to a proper sentinel model:

- io_read_kb / io_write_kb change from u64 to Option<u64>
- io_total_kb() returns Option<u64>; None when either field is None
- SortMode::Io uses 4-arm match on (a_total, b_total):
    * both Some -> descending by total
    * Some/None -> known sorts above unknown
    * None/None -> stable tie (input order)
- Render layer shows em-dash (\u2014) when total is None
- Single-pass /proc/[pid]/io parse replaces two-helper double-read
  (read_io_file returns Option<(u64, u64)> in bytes; caller /1024s
  to KiB so the None sentinel propagates end-to-end)
- #[allow(dead_code)] on ppid, vsize_kb with documented future use
  (process tree view, memory detail panel) per project warning policy

Test count 80 -> 83:
- Replaced misleading 'io_total_saturates_on_underflow' (tested
  normal sum) with 'io_total_saturates_at_u64_max' (genuine edge)
- Added 'io_total_returns_none_when_fields_missing'
- Added 'sort_by_io_pushes_missing_to_bottom'
- Added 'io_name_is_io' to lock the SortMode::Io.name() string

Compile warnings 56 -> 55 (the ppid/vsize_kb dead_code warning
is now suppressed; remaining 55 are pre-existing in other modules).

Redox stripped binary: 4,127,592 bytes (+4 KiB from v1.22).
Linux smoke test confirms em-dash renders for kscreenlocker_g,
kwin_wayland, tailscaled, polkit-kde-auth (all owned-UID procs
that the kernel hides /proc/[pid]/io from on this dev host).

Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA747
2026-06-21 00:20:45 +03:00