Files
RedBear-OS/local/recipes/system/redbear-power/source/src/bench.rs
T
vasilito ed92bce14b redbear-power: v1.1 — full Phase A→D implementation
Comprehensive implementation per local/docs/redbear-power-improvement-plan.md.

Source: 2376 LoC across 10 modules (was 1396/6 in v0.6, +980 LoC).
Cross-compile: 2.8 MB stripped Redox ELF binary.
SHA256: 1b6f9db6ce79e77957bbb1fd606c430516015d5f02f3b64cb6f395e2f63b8e04

Modules:
- main.rs     (376) — event loop, key + mouse dispatch, render orchestration
- app.rs      (421) — App, CpuRow, Governor, ThrottleMode, PackageThermal, HybridInfo
- render.rs   (498) — header/table/controls/help/snapshot rendering
- acpi.rs     (166) — CPU enumeration, ACPI _PSS, CPUID fallback
- cpuid.rs    (350) — CPUID leaf decoding (vendor, family, model, SIMD, cache, hybrid)
- bench.rs    (123) — prime-sieve stress benchmark for thermal response testing
- dbus.rs     (202) — D-Bus export via zbus 5 (org.redbear.Power, --dbus flag)
- msr.rs      (127) — MSR constants + PackageThermal decoder
- cpufreq.rs  (50)  — governor hint read/write
- theme.rs    (72)  — central color palette (const Style)

Phase A — bug fixes:
- R1: PROCHOT pulse bug — Instant::now() math always ~0, pulse never toggled.
  Replaced with Frame::count() so the bar pulses at a frame-rate-stable rate.
- R5: removed duplicate comment block in snapshot().
- C2: PackageThermal struct + 13 PKG_THERM_* bit constants; full decode of
  IA32_PACKAGE_THERM_STATUS (PL1/PL2/CRIT/TT1/TT2/HFI/temp) surfaced in header.

Phase B — quality:
- R3: input poll decoupled from refresh cadence (50ms vs 250-2000ms).
- R4: Rect::centered replaces hand-rolled centered_rect helper.
- R6: area.layout(&Layout) destructuring with compile-time size check.
- O2: theme.rs central color palette (LABEL, BORDER_*, STATUS_*).
- C9: ratatui 0.30 Stylize shorthand across all renders.

Phase C — features:
- C1/C8: cpuid.rs reads leaves 0/1/4/7/0x80000000+/0x1A/0x8000001E.
- C3: SIMD display header line.
- C5: cache hierarchy header line.
- C7: dynamic refresh interval via / key (typed input 50..60000ms + Enter).
- C6: prime-sieve benchmark via b/B keys (one thread per core, AtomicU64
  counter, run/stop/status).

Phase D remaining (was deferred per plan s23):
- C4: hybrid CPU detection (CoreType enum, Intel leaf 0x1A, AMD leaf 0x8000001E),
  per-CPU row prefixed with type label, Hybrid: 8P + 16E header.
- O1: termion MouseTerminal wrapper enables xterm mouse protocols.
  Wheel = scroll, Left-click = select/toggle, Right-click = expand P-state.
  hit_test() maps (x, y) to panel rects cached after every render.
- O3: dbus.rs publishes org.redbear.Power on session bus (opt-in via --dbus).
  Properties: cpu_count, avg_freq_khz, max_temp_c, avg_load_pct, governor,
  throttle_mode, prochot_asserted. Background thread owns the tokio runtime
  + zbus Connection; main thread sends snapshots via mpsc channel. Graceful
  degradation if redbear-sessiond is unreachable.

Verification:
- cargo build --release (host): 0 errors, 21 warnings.
- ./redbear-power --once (Linux host, AMD 24-core): renders all features.
- ./redbear-power --dbus (via script(1)): registers on session bus,
  emits xterm mouse capture sequences.
- cook redbear-power (Redox target): 2.8 MB stripped binary at
  local/recipes/system/redbear-power/target/x86_64-unknown-redox/stage/usr/bin/redbear-power.

ISO rebuild status: blocked by pre-existing upstream nix-0.30.1 vs Redox
relibc SaFlags incompatibility in uutils (recipes/core/uutils). The v1.1
binary IS staged and will be packaged into the next successful ISO build
once that issue is resolved (separate scope).

Docs:
- local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md s3.3.2 - v1.0 + v1.1 sections.
- local/docs/redbear-power-improvement-plan.md s24 - full status update.
- local/docs/RATATUI-APP-PATTERNS.md - canonical ratatui 0.30 guide
  (1161 lines), includes s13 ratatui 0.30 Best-Practices Update + s14
  Cross-Reference redbear-power as a Reference Implementation.

Cargo.toml: new dependencies zbus = "5" (tokio feature) and tokio = "1"
(rt + rt-multi-thread + macros) for the D-Bus export.
2026-06-20 13:26:24 +03:00

123 lines
4.1 KiB
Rust

//! Lightweight CPU stress benchmark for thermal response testing.
//!
//! Spawns one thread per core that runs a prime-sieve-style loop until
//! the user presses 'B' to stop, or the duration expires. The benchmark
//! is intentionally simple (no FFT, no AES-NI, no AVX) so it works on
//! the lowest-common-denominator hardware and exercises enough load to
//! observe thermal headroom behavior.
//!
//! Pattern matches cpu-x `core/benchmarks.cpp:primes_bench` but in Rust.
//! All work is done on the spawned threads; the main thread just
//! collects the running total via `AtomicU64`.
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
pub struct Bench {
pub running: bool,
pub started_at: Option<Instant>,
pub duration: Duration,
pub primes_found: Arc<AtomicU64>,
pub cancel: Arc<AtomicBool>,
pub threads: Vec<JoinHandle<()>>,
pub last_score: u64,
pub last_duration_s: u32,
}
impl Default for Bench {
fn default() -> Self {
Self {
running: false,
started_at: None,
duration: Duration::from_secs(30),
primes_found: Arc::new(AtomicU64::new(0)),
cancel: Arc::new(AtomicBool::new(false)),
threads: Vec::new(),
last_score: 0,
last_duration_s: 0,
}
}
}
impl Bench {
pub fn start(&mut self, num_cores: usize, duration_s: u32) {
if self.running {
return;
}
self.duration = Duration::from_secs(duration_s as u64);
self.primes_found.store(0, Ordering::Relaxed);
self.cancel.store(false, Ordering::Relaxed);
self.started_at = Some(Instant::now());
self.running = true;
let primes = Arc::clone(&self.primes_found);
let cancel = Arc::clone(&self.cancel);
let duration = self.duration;
let cores = num_cores.max(1);
for _ in 0..cores {
let primes = Arc::clone(&primes);
let cancel = Arc::clone(&cancel);
self.threads.push(thread::spawn(move || {
let start = Instant::now();
let mut n: u64 = 1;
while !cancel.load(Ordering::Relaxed) && start.elapsed() < duration {
n += 1;
let mut is_prime = n >= 2;
let mut i: u64 = 2;
while i * i <= n && is_prime {
if n % i == 0 {
is_prime = false;
}
i += 1;
}
if is_prime {
primes.fetch_add(1, Ordering::Relaxed);
}
}
}));
}
}
pub fn stop(&mut self) {
if !self.running {
return;
}
self.cancel.store(true, Ordering::Relaxed);
let elapsed = self.started_at.map(|s| s.elapsed()).unwrap_or_default();
self.last_duration_s = elapsed.as_secs() as u32;
self.last_score = self.primes_found.load(Ordering::Relaxed);
for h in self.threads.drain(..) {
let _ = h.join();
}
self.running = false;
self.started_at = None;
}
pub fn progress(&self) -> Option<(u32, u64)> {
if !self.running {
return None;
}
let elapsed = self.started_at?.elapsed().as_secs() as u32;
Some((elapsed, self.primes_found.load(Ordering::Relaxed)))
}
pub fn status_line(&self, num_cores: usize) -> String {
if let Some((elapsed, primes)) = self.progress() {
format!(
"Bench: prime sieve ({}s elapsed, {} primes, {} threads)",
elapsed,
primes,
num_cores.max(1)
)
} else if self.last_score > 0 {
format!(
"Bench: last run = {} primes in {}s",
self.last_score, self.last_duration_s
)
} else {
"Bench: idle (press 'b' to start)".to_string()
}
}
}