ed92bce14b
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.
123 lines
4.1 KiB
Rust
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()
|
|
}
|
|
}
|
|
} |