josie / alder-tools

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

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 {