From 7aa811833816f1496a6bbc6e304e1b45ce17b8b5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 3 Nov 2026 06:01:14 +0000 Subject: [PATCH] fix: keep an unknown GPU memory limit out of the history A missing or zero dedicated or shared limit is not measured 0% usage. Leave the previous history point, and still record a real zero once a non-zero limit is available. Co-authored-by: jameslincn --- src/pages/gpu/mod.rs | 90 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 8 deletions(-) diff --git a/src/pages/gpu/mod.rs b/src/pages/gpu/mod.rs index 6657b50..26557ea 100644 --- a/src/pages/gpu/mod.rs +++ b/src/pages/gpu/mod.rs @@ -308,14 +308,16 @@ impl AdapterHistory { .all(|engine_id| self.engine_histories.contains_key(engine_id)) ); - if let Some(usage) = sample.dedicated_usage_bytes { - self.dedicated_history - .push(memory_percentage(usage, sample.info.dedicated_limit_bytes)); - } - if let Some(usage) = sample.shared_usage_bytes { - self.shared_history - .push(memory_percentage(usage, sample.info.shared_limit_bytes)); - } + record_memory_history( + &mut self.dedicated_history, + sample.dedicated_usage_bytes, + sample.info.dedicated_limit_bytes, + ); + record_memory_history( + &mut self.shared_history, + sample.shared_usage_bytes, + sample.info.shared_limit_bytes, + ); } fn engine_options(&self) -> Vec<(GpuEngineId, GpuEngineKind)> { @@ -1685,6 +1687,19 @@ fn memory_percentage(usage: u64, limit: Option) -> u8 { rounded.min(100) as u8 } +fn record_memory_history(history: &mut HistoryBuffer, usage: Option, limit: Option) { + // A missing or zero limit makes memory_percentage return 0. That clamp is not a measured + // empty GPU, so this sample must not advance the graph. A later sample with a non-zero + // limit and real 0% usage still advances. + let Some(usage) = usage else { + return; + }; + if limit.filter(|limit| *limit != 0).is_none() { + return; + } + history.push(memory_percentage(usage, limit)); +} + fn status_after_refresh_error(has_snapshot: bool, unsupported: bool) -> GpuPageStatus { if has_snapshot { GpuPageStatus::Stale @@ -1964,6 +1979,65 @@ mod tests { assert_eq!(memory_percentage(1, Some(0)), 0); } + fn adapter_with_limits( + dedicated_limit_bytes: Option, + shared_limit_bytes: Option, + ) -> Arc { + let mut info = (*staged_adapter()).clone(); + info.dedicated_limit_bytes = dedicated_limit_bytes; + info.shared_limit_bytes = shared_limit_bytes; + Arc::new(info) + } + + fn memory_sample( + info: &Arc, + dedicated_usage_bytes: Option, + shared_usage_bytes: Option, + ) -> GpuAdapterSample { + GpuAdapterSample { + info: Arc::clone(info), + overall_utilization_percent: None, + engines: Vec::new(), + dedicated_usage_bytes, + shared_usage_bytes, + temperature_deci_c: None, + row_errors: Vec::new(), + } + } + + fn history_values(history: &HistoryBuffer) -> Vec { + (0..history.len()) + .map(|index| history.value_at(index).unwrap()) + .collect() + } + + #[test] + fn unknown_memory_limit_does_not_advance_history() { + let mut history = AdapterHistory::new(); + let limited = adapter_with_limits(Some(1_000), Some(2_000)); + history.push(&memory_sample(&limited, Some(500), Some(500))); + assert_eq!(history.dedicated_history.newest_value(), 50); + assert_eq!(history.shared_history.newest_value(), 25); + let dedicated_before = history_values(&history.dedicated_history); + let shared_before = history_values(&history.shared_history); + + let missing_limit = adapter_with_limits(None, None); + history.push(&memory_sample(&missing_limit, Some(1), Some(1))); + assert_eq!(history_values(&history.dedicated_history), dedicated_before); + assert_eq!(history_values(&history.shared_history), shared_before); + + let zero_limit = adapter_with_limits(Some(0), Some(0)); + history.push(&memory_sample(&zero_limit, Some(1), Some(1))); + assert_eq!(history_values(&history.dedicated_history), dedicated_before); + assert_eq!(history_values(&history.shared_history), shared_before); + + history.push(&memory_sample(&limited, Some(0), Some(0))); + assert_eq!(history.dedicated_history.newest_value(), 0); + assert_eq!(history.shared_history.newest_value(), 0); + assert_eq!(history.dedicated_history.value_at(1), Some(50)); + assert_eq!(history.shared_history.value_at(1), Some(25)); + } + #[test] fn refresh_errors_only_claim_a_stale_sample_when_one_exists() { assert_eq!(