diff --git a/adlermon.conf b/adlermon.conf
index 03dfd9c..328e1d7 100644
--- a/adlermon.conf
+++ b/adlermon.conf
@@ -30,8 +30,8 @@ rail.in12.label = Fixed 1.14
rail.in13.hide = true
rail.in14.label = Fixed 1.5V
poll_ms = 250
-graph_secs = 1800
-graph_scale_fixed = true
+graph_secs = 300
+graph_scale_fixed = false
graphs = true
layout = auto
vcore_limit = 1.403
diff --git a/src/config.rs b/src/config.rs
index a6da5c8..c667c60 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -58,7 +58,7 @@ impl Default for Config {
layout: Layout::Auto,
graphs: true,
poll_ms: 250,
- graph_secs: 120,
+ graph_secs: 300,
graph_scale_fixed: false,
loaded_path: None,
}
diff --git a/src/plot.rs b/src/plot.rs
index 78d6d9f..1e541f5 100644
--- a/src/plot.rs
+++ b/src/plot.rs
@@ -5,6 +5,9 @@
//! RIGHT-ANGLE corners (level `─`, corners `┐└` on falls / `┘┌` on rises,
//! `│` between) so peaks/valleys read as sharp steps. Y-axis: max top-left,
//! 0 bottom-left. No time axis (all stacked plots share one window).
+//! Scrolling view: the last `cols` ticks fill the width 1:1 — no
+//! decimation, no smoothing, so spikes read raw; older history stays in the
+//! ring for the peak counter and scale.
use ratatui::{
Frame,
@@ -84,14 +87,16 @@ fn levels_hysteresis(samples: &[f64], max: f64, rows: usize) -> Vec<usize> {
}
};
out.push(row as usize);
- last = Some(row as f64);
+ last = Some(row);
}
out
}
/// Thin-line draw: y-axis labels overlay the left edge (max top-left,
/// 0 bottom-left), the staircase occupies the full area. No time axis —
-/// stacked plots share one window, so x is time on every plot.
+/// stacked plots share one window, so x is time on every plot. Scrolling
+/// view: the last `cols` ticks fill the width 1:1 (newest at the right
+/// edge), older history scrolls off the left — no decimation or smoothing.
pub fn render(
f: &mut Frame,
area: Rect,
@@ -107,44 +112,15 @@ pub fn render(
let rows = area.height as usize;
let cols = area.width as usize;
- // nvtop's ring holds exactly one sample per plot column — no draw-time
- // resampling, so nothing aliases. When our window holds more ticks than
- // columns, decimate by MAX per column (peak-preserving; mean/stride
- // made adjacent columns alternate rows → dash artifacts). Buckets run
- // oldest→newest so the newest sample lands in the rightmost column.
+ // Scrolling view: one sample per column, newest at the right edge.
+ // The window is the visible width, not the whole graph_secs history —
+ // the ring keeps the full window for the peak counter and scale.
let samples: Vec<f64> = ring.samples_window(window_ticks).collect();
if samples.is_empty() {
return;
}
- let shown: Vec<f64> = if samples.len() > cols {
- // EMA-smooth first (alpha 0.35): idle wattage jitters ±0.3 W per
- // tick and max-decimation amplifies a single jumpy sample into a
- // 1-row flip that defeats row hysteresis. Subagent-diagnosed.
- let a = 0.35;
- let mut ema = samples[0];
- let smoothed: Vec<f64> = samples
- .iter()
- .map(|&v| {
- ema = a * v + (1.0 - a) * ema;
- ema
- })
- .collect();
- let bucket = smoothed.len() / cols;
- let rem = smoothed.len() % cols;
- let mut out = Vec::with_capacity(cols);
- let mut idx = 0;
- for c in 0..cols {
- let extra = if c < rem { 1 } else { 0 };
- let end = idx + bucket + extra;
- out.push(smoothed[idx..end].iter().cloned().fold(f64::MIN, f64::max));
- idx = end;
- }
- out
- } else {
- samples
- };
- let skip = shown.len().saturating_sub(cols);
- let lvls = levels_hysteresis(&shown[skip..], max, rows);
+ let skip = samples.len().saturating_sub(cols);
+ let lvls = levels_hysteresis(&samples[skip..], max, rows);
let mut buf = vec![vec![(' ', Style::default()); cols]; rows];
let mut set = |r: usize, c: usize, ch: char, st: Style| {
diff --git a/src/ui.rs b/src/ui.rs
index eadcccd..ef5aa68 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -50,6 +50,10 @@ pub struct App {
temp_graph: Ring,
/// Peak clock (max freq across all cores per tick) graph history.
clock_graph: Ring,
+ /// Peak clock (max freq across all cores per tick) — current + peak.
+ clock_khz: Option<u64>,
+ /// Highest peak-clock ever seen this session (auto-scale anchor).
+ clock_all_time_peak: u64,
/// Settings pane open (F2); F10 commits+writes conf, Esc closes.
settings_open: bool,
/// Which settings row the cursor is on (index into SETTINGS_ROWS).
@@ -186,6 +190,8 @@ impl App {
watts_graph: Ring::new(cap),
temp_graph: Ring::new(cap),
clock_graph: Ring::new(cap),
+ clock_khz: None,
+ clock_all_time_peak: 0,
settings_open: false,
settings_row: 0,
edit_buf: String::new(),
@@ -271,6 +277,8 @@ impl App {
// Peak clock: max freq across all cores this tick (kHz).
if let Some(peak_khz) = self.cores.iter().filter_map(|c| c.freq_khz).max() {
self.clock_graph.push(peak_khz as f64);
+ self.clock_khz = Some(peak_khz);
+ self.clock_all_time_peak = self.clock_all_time_peak.max(peak_khz);
}
}
@@ -532,9 +540,12 @@ pub fn draw(f: &mut Frame, app: &App) {
// Graphs scale to the WINDOW's max (recent history), not the
// session all-time peak — a flat idle line pinned to the bottom of
// a 1.5 V scale reads as broken. Hero bar keeps all-time scale.
- let ticks = graph_ticks(&app.cfg);
+ // Scrolling view: the plot shows the last `w` ticks (one per column,
+ // newest at the right edge); the ring keeps the full graph_secs
+ // history for the peak counter and scale.
+ let ticks = w as usize;
let ring_max = |ring: &Ring, floor: f64| -> f64 {
- (ring.max_window(ticks) * (1.0 + SCALE_MARGIN)).max(floor)
+ (ring.max_window(graph_ticks(&app.cfg)) * (1.0 + SCALE_MARGIN)).max(floor)
};
// Fixed scale (settings pane / graph_scale_fixed): bar_max limits,
// never rescaled mid-benchmark — peaks clip at the top instead of
@@ -555,18 +566,24 @@ pub fn draw(f: &mut Frame, app: &App) {
ring_max(&app.clock_graph, 1_000_000.0),
)
};
- let clock_text = match app.cores.iter().filter_map(|c| c.freq_khz).max() {
- Some(k) => format!("{:.2} GHz", k as f64 / 1_000_000.0),
- None => "n/a".to_string(),
+ let clock_text = match (app.clock_khz, app.clock_all_time_peak) {
+ (Some(k), p) if p > 0 => format!(
+ "{:.2} GHz peak {:.2}",
+ k as f64 / 1_000_000.0,
+ p as f64 / 1_000_000.0
+ ),
+ (Some(k), _) => format!("{:.2} GHz", k as f64 / 1_000_000.0),
+ (None, _) => "n/a".to_string(),
};
// 4 full-width plots stacked top→bottom, sharing one time axis.
+ // Cascade: vCore → Peak clock → Pkg power → Pkg temp (user order).
let plots: [PlotSpec; 4] = [
("vCore", hero_text.clone(), &app.vcore_graph, vc_graph_max, vc_color,
Some(((1.0 - (app.cfg.vcore_limit / vc_graph_max).clamp(0.0, 1.0)) * (graph_rows - 1) as f64).round() as u16)),
+ ("Peak clock", clock_text, &app.clock_graph, clock_graph_max, Color::Green, None),
("Pkg power", fmt_opt_watts(app.package_watts), &app.watts_graph, watt_graph_max, Color::Blue, None),
("Pkg temp", fmt_opt_temp(app.package_temp), &app.temp_graph, temp_graph_max,
app.package_temp.map_or(Color::DarkGray, |t| temp_color(t, app.cfg.temp_warn, app.cfg.temp_crit)), None),
- ("Peak clock", clock_text, &app.clock_graph, clock_graph_max, Color::Green, None),
];
let mut y = bottom_y;
for (label, text, ring, max, color, marker) in plots {
Graphs: scrolling view (last width ticks 1:1, no decimation/EMA — spikes read raw), default 5-min window, peak-clock counter (cur GHz peak GHz), cascade order vCore → Peak clock → Pkg power → Pkg temp; conf reset to 300s/auto
f61cc8055384c06fed899883983f26740aa1150f
josie <administrator@josie-c.com> · 2026-08-31T18:38 ·
browse files at this commit
parents:
7e801e5