simplebench_runtime/
cpu_monitor.rs1use std::fs;
7use std::time::Instant;
8
9pub struct CpuMonitor {
11 cpu_core: usize,
12 thermal_zone: Option<usize>,
13}
14
15impl CpuMonitor {
16 pub fn new(cpu_core: usize) -> Self {
18 let thermal_zone = Self::discover_thermal_zones().first().copied();
19 Self {
20 cpu_core,
21 thermal_zone,
22 }
23 }
24
25 pub fn read_frequency(&self) -> Option<u64> {
27 #[cfg(target_os = "linux")]
28 {
29 let path = format!(
30 "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_cur_freq",
31 self.cpu_core
32 );
33 fs::read_to_string(path)
34 .ok()
35 .and_then(|s| s.trim().parse().ok())
36 }
37 #[cfg(not(target_os = "linux"))]
38 {
39 None
40 }
41 }
42
43 pub fn read_governor(&self) -> Option<String> {
45 #[cfg(target_os = "linux")]
46 {
47 let path = format!(
48 "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_governor",
49 self.cpu_core
50 );
51 fs::read_to_string(path).ok().map(|s| s.trim().to_string())
52 }
53 #[cfg(not(target_os = "linux"))]
54 {
55 None
56 }
57 }
58
59 pub fn read_frequency_range(&self) -> Option<(u64, u64)> {
61 #[cfg(target_os = "linux")]
62 {
63 let min_path = format!(
64 "/sys/devices/system/cpu/cpu{}/cpufreq/cpuinfo_min_freq",
65 self.cpu_core
66 );
67 let max_path = format!(
68 "/sys/devices/system/cpu/cpu{}/cpufreq/cpuinfo_max_freq",
69 self.cpu_core
70 );
71
72 let min = fs::read_to_string(min_path)
73 .ok()
74 .and_then(|s| s.trim().parse().ok())?;
75 let max = fs::read_to_string(max_path)
76 .ok()
77 .and_then(|s| s.trim().parse().ok())?;
78
79 Some((min, max))
80 }
81 #[cfg(not(target_os = "linux"))]
82 {
83 None
84 }
85 }
86
87 pub fn read_temperature(&self) -> Option<i32> {
89 #[cfg(target_os = "linux")]
90 {
91 let zone = self.thermal_zone?;
92 let path = format!("/sys/class/thermal/thermal_zone{}/temp", zone);
93 fs::read_to_string(path)
94 .ok()
95 .and_then(|s| s.trim().parse().ok())
96 }
97 #[cfg(not(target_os = "linux"))]
98 {
99 None
100 }
101 }
102
103 pub fn discover_thermal_zones() -> Vec<usize> {
105 #[cfg(target_os = "linux")]
106 {
107 (0..20)
108 .filter(|&i| {
109 let path = format!("/sys/class/thermal/thermal_zone{}/temp", i);
110 fs::metadata(path).is_ok()
111 })
112 .collect()
113 }
114 #[cfg(not(target_os = "linux"))]
115 {
116 Vec::new()
117 }
118 }
119}
120
121#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
123pub struct CpuSnapshot {
124 #[serde(skip, default = "Instant::now")]
125 pub timestamp: Instant,
126 pub frequency_khz: Option<u64>,
127 pub temperature_millic: Option<i32>,
128}
129
130impl CpuSnapshot {
131 pub fn frequency_mhz(&self) -> Option<f64> {
133 self.frequency_khz.map(|khz| khz as f64 / 1000.0)
134 }
135
136 pub fn temperature_celsius(&self) -> Option<f64> {
138 self.temperature_millic.map(|millic| millic as f64 / 1000.0)
139 }
140}
141
142impl Default for CpuSnapshot {
143 fn default() -> Self {
144 Self {
145 timestamp: Instant::now(),
146 frequency_khz: None,
147 temperature_millic: None,
148 }
149 }
150}
151
152pub fn verify_benchmark_environment(cpu_core: usize) {
154 use colored::*;
155
156 println!("{}", "Verifying benchmark environment...".green().bold());
157
158 #[cfg(target_os = "linux")]
159 {
160 println!(
161 " {} {}",
162 "Platform:".dimmed(),
163 "Linux (full monitoring support)".cyan()
164 );
165
166 let monitor = CpuMonitor::new(cpu_core);
167
168 if let Some(governor) = monitor.read_governor() {
170 print!(" {} {} {}", "CPU".dimmed(), cpu_core, "governor:".dimmed());
171 if governor == "performance" {
172 println!(" {}", governor.green());
173 } else {
174 println!(" {}", governor.yellow());
175 println!(
176 " {} {}",
177 "⚠".yellow(),
178 "Not using 'performance' governor".yellow()
179 );
180 println!(
181 " {} {}",
182 "Consider:".dimmed(),
183 "sudo cpupower frequency-set -g performance".dimmed()
184 );
185 }
186 }
187
188 if let Some((min_khz, max_khz)) = monitor.read_frequency_range() {
190 println!(
191 " {} {} {} {} {} {} {}",
192 "CPU".dimmed(),
193 cpu_core,
194 "frequency range:".dimmed(),
195 (min_khz / 1000).to_string().cyan(),
196 "MHz -".dimmed(),
197 (max_khz / 1000).to_string().cyan(),
198 "MHz".dimmed()
199 );
200 }
201
202 if let Some(freq_khz) = monitor.read_frequency() {
204 println!(
205 " {} {} {} {} {}",
206 "CPU".dimmed(),
207 cpu_core,
208 "current frequency:".dimmed(),
209 (freq_khz / 1000).to_string().cyan(),
210 "MHz".dimmed()
211 );
212 }
213
214 let zones = CpuMonitor::discover_thermal_zones();
216 if !zones.is_empty() {
217 println!(
218 " {} {} {}",
219 "Found".dimmed(),
220 zones.len().to_string().cyan(),
221 "thermal zone(s)".dimmed()
222 );
223 for zone in zones.iter().take(3) {
224 let path = format!("/sys/class/thermal/thermal_zone{}/temp", zone);
225 if let Ok(temp_str) = fs::read_to_string(path) {
226 if let Ok(temp_millic) = temp_str.trim().parse::<i32>() {
227 println!(
228 " {} {} {}°C",
229 "Zone".dimmed(),
230 zone,
231 (temp_millic / 1000).to_string().cyan()
232 );
233 }
234 }
235 }
236 }
237 }
238
239 #[cfg(not(target_os = "linux"))]
240 {
241 let os = std::env::consts::OS;
242 println!(
243 " {} {} {}",
244 "Platform:".dimmed(),
245 os.cyan(),
246 "(limited monitoring support)".dimmed()
247 );
248 println!(
249 " {} {}",
250 "ℹ".blue(),
251 "CPU frequency/thermal monitoring not available on this platform".dimmed()
252 );
253 }
254
255 println!("{}\n", "Environment check complete.".green());
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_cpu_monitor_creation() {
264 let monitor = CpuMonitor::new(0);
265 let _ = monitor.read_frequency();
267 let _ = monitor.read_governor();
268 let _ = monitor.read_frequency_range();
269 let _ = monitor.read_temperature();
270 }
271
272 #[test]
273 fn test_thermal_zone_discovery() {
274 let zones = CpuMonitor::discover_thermal_zones();
275 #[cfg(target_os = "linux")]
278 {
279 println!("Found {} thermal zones", zones.len());
281 }
282 #[cfg(not(target_os = "linux"))]
283 {
284 assert!(zones.is_empty());
285 }
286 }
287
288 #[test]
289 fn test_cpu_snapshot() {
290 let snapshot = CpuSnapshot {
291 timestamp: Instant::now(),
292 frequency_khz: Some(4500000),
293 temperature_millic: Some(55000),
294 };
295
296 assert_eq!(snapshot.frequency_mhz(), Some(4500.0));
297 assert_eq!(snapshot.temperature_celsius(), Some(55.0));
298 }
299
300 #[test]
301 fn test_verify_environment() {
302 verify_benchmark_environment(0);
304 }
305}