Skip to content

Commit e2fba06

Browse files
committed
feat: smooth temp if no sensor value on m3/m4 chips #12
1 parent dac7c39 commit e2fba06

File tree

3 files changed

+68
-24
lines changed

3 files changed

+68
-24
lines changed

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ This will collect 10 samples with an update interval of 500 milliseconds.
112112

113113
```jsonc
114114
{
115+
"timestamp": "2025-02-24T20:38:15.427569+00:00",
115116
"temp": {
116117
"cpu_temp_avg": 43.73614, // Celsius
117118
"gpu_temp_avg": 36.95167 // Celsius

src/app.rs

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@ use ratatui::{prelude::*, widgets::*};
1111

1212
use crate::config::{Config, ViewType};
1313
use crate::metrics::{Metrics, Sampler, zero_div};
14-
use crate::{
15-
metrics::{MemMetrics, TempMetrics},
16-
sources::SocInfo,
17-
};
14+
use crate::{metrics::MemMetrics, sources::SocInfo};
1815

1916
type WithError<T> = Result<T, Box<dyn std::error::Error>>;
2017

2118
const GB: u64 = 1024 * 1024 * 1024;
2219
const MAX_SPARKLINE: usize = 128;
20+
const MAX_TEMPS: usize = 8;
2321

2422
// MARK: Term utils
2523

@@ -44,14 +42,6 @@ fn leave_term() {
4442

4543
// MARK: Storage
4644

47-
fn items_add<T>(vec: &mut Vec<T>, val: T) -> &Vec<T> {
48-
vec.insert(0, val);
49-
if vec.len() > MAX_SPARKLINE {
50-
vec.pop();
51-
}
52-
vec
53-
}
54-
5545
#[derive(Debug, Default)]
5646
struct FreqStore {
5747
items: Vec<u64>, // from 0 to 100
@@ -61,7 +51,9 @@ struct FreqStore {
6151

6252
impl FreqStore {
6353
fn push(&mut self, value: u64, usage: f64) {
64-
items_add(&mut self.items, (usage * 100.0) as u64);
54+
self.items.insert(0, (usage * 100.0) as u64);
55+
self.items.truncate(MAX_SPARKLINE);
56+
6557
self.top_value = value;
6658
self.usage = usage;
6759
}
@@ -77,8 +69,11 @@ struct PowerStore {
7769

7870
impl PowerStore {
7971
fn push(&mut self, value: f64) {
80-
let was_top = if self.items.len() > 0 { self.items[0] as f64 / 1000.0 } else { 0.0 };
81-
items_add(&mut self.items, (value * 1000.0) as u64);
72+
let was_top = if !self.items.is_empty() { self.items[0] as f64 / 1000.0 } else { 0.0 };
73+
74+
self.items.insert(0, (value * 1000.0) as u64);
75+
self.items.truncate(MAX_SPARKLINE);
76+
8277
self.top_value = avg2(was_top, value);
8378
self.avg_value = self.items.iter().sum::<u64>() as f64 / self.items.len() as f64 / 1000.0;
8479
self.max_value = self.items.iter().max().map_or(0, |v| *v) as f64 / 1000.0;
@@ -97,7 +92,9 @@ struct MemoryStore {
9792

9893
impl MemoryStore {
9994
fn push(&mut self, value: MemMetrics) {
100-
items_add(&mut self.items, value.ram_usage);
95+
self.items.insert(0, value.ram_usage);
96+
self.items.truncate(MAX_SPARKLINE);
97+
10198
self.ram_usage = value.ram_usage;
10299
self.ram_total = value.ram_total;
103100
self.swap_usage = value.swap_usage;
@@ -106,6 +103,46 @@ impl MemoryStore {
106103
}
107104
}
108105

106+
#[derive(Debug, Default)]
107+
struct TempStore {
108+
items: Vec<f32>,
109+
}
110+
111+
impl TempStore {
112+
fn last(&self) -> f32 {
113+
*self.items.first().unwrap_or(&0.0)
114+
}
115+
116+
fn push(&mut self, value: f32) {
117+
// https://www.tunabellysoftware.com/blog/files/tg-pro-apple-silicon-m3-series-support.html
118+
// https://github.com/vladkens/macmon/issues/12
119+
let value = if value == 0.0 { self.trend_ema(0.8) } else { value };
120+
if value == 0.0 {
121+
return; // skip if not sensor available
122+
}
123+
124+
self.items.insert(0, value);
125+
self.items.truncate(MAX_TEMPS);
126+
}
127+
128+
// https://en.wikipedia.org/wiki/Exponential_smoothing
129+
fn trend_ema(&self, alpha: f32) -> f32 {
130+
if self.items.len() < 2 {
131+
return 0.0;
132+
}
133+
134+
// starts from most recent value, so need to be reversed
135+
let mut iter = self.items.iter().rev();
136+
let mut ema = *iter.next().unwrap_or(&0.0);
137+
138+
for &item in iter {
139+
ema = alpha * item + (1.0 - alpha) * ema;
140+
}
141+
142+
ema
143+
}
144+
}
145+
109146
// MARK: Components
110147

111148
fn h_stack(area: Rect) -> (Rect, Rect) {
@@ -178,7 +215,7 @@ fn run_sampler_thread(tx: mpsc::Sender<Event>, msec: Arc<RwLock<u32>>) {
178215
});
179216
}
180217

181-
// get avaerage of two values, used to smooth out metrics
218+
// get average of two values, used to smooth out metrics
182219
// see: https://github.com/vladkens/macmon/issues/10
183220
fn avg2<T: num_traits::Float>(a: T, b: T) -> T {
184221
return if a == T::zero() { b } else { (a + b) / T::from(2.0).unwrap() };
@@ -192,14 +229,16 @@ pub struct App {
192229

193230
soc: SocInfo,
194231
mem: MemoryStore,
195-
temp: TempMetrics,
196232

197233
cpu_power: PowerStore,
198234
gpu_power: PowerStore,
199235
ane_power: PowerStore,
200236
all_power: PowerStore,
201237
sys_power: PowerStore,
202238

239+
cpu_temp: TempStore,
240+
gpu_temp: TempStore,
241+
203242
ecpu_freq: FreqStore,
204243
pcpu_freq: FreqStore,
205244
igpu_freq: FreqStore,
@@ -222,8 +261,8 @@ impl App {
222261
self.pcpu_freq.push(data.pcpu_usage.0 as u64, data.pcpu_usage.1 as f64);
223262
self.igpu_freq.push(data.gpu_usage.0 as u64, data.gpu_usage.1 as f64);
224263

225-
self.temp.cpu_temp_avg = avg2(self.temp.cpu_temp_avg, data.temp.cpu_temp_avg);
226-
self.temp.gpu_temp_avg = avg2(self.temp.gpu_temp_avg, data.temp.gpu_temp_avg);
264+
self.cpu_temp.push(data.temp.cpu_temp_avg);
265+
self.gpu_temp.push(data.temp.gpu_temp_avg);
227266

228267
self.mem.push(data.memory);
229268
}
@@ -388,8 +427,8 @@ impl App {
388427
.constraints([Constraint::Fill(1), Constraint::Fill(1), Constraint::Fill(1)].as_ref())
389428
.split(iarea);
390429

391-
f.render_widget(self.get_power_block("CPU", &self.cpu_power, self.temp.cpu_temp_avg), ha[0]);
392-
f.render_widget(self.get_power_block("GPU", &self.gpu_power, self.temp.gpu_temp_avg), ha[1]);
430+
f.render_widget(self.get_power_block("CPU", &self.cpu_power, self.cpu_temp.last()), ha[0]);
431+
f.render_widget(self.get_power_block("GPU", &self.gpu_power, self.gpu_temp.last()), ha[1]);
393432
f.render_widget(self.get_power_block("ANE", &self.ane_power, 0.0), ha[2]);
394433
}
395434

src/metrics.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,18 @@ impl Sampler {
155155
for sensor in &self.smc_cpu_keys {
156156
let val = self.smc.read_val(sensor)?;
157157
let val = f32::from_le_bytes(val.data[0..4].try_into().unwrap());
158-
cpu_metrics.push(val);
158+
if val != 0.0 {
159+
cpu_metrics.push(val);
160+
}
159161
}
160162

161163
let mut gpu_metrics = Vec::new();
162164
for sensor in &self.smc_gpu_keys {
163165
let val = self.smc.read_val(sensor)?;
164166
let val = f32::from_le_bytes(val.data[0..4].try_into().unwrap());
165-
gpu_metrics.push(val);
167+
if val != 0.0 {
168+
gpu_metrics.push(val);
169+
}
166170
}
167171

168172
let cpu_temp_avg = zero_div(cpu_metrics.iter().sum::<f32>(), cpu_metrics.len() as f32);

0 commit comments

Comments
 (0)