Skip to main content

auto_cpufreq/modules/
system_monitor.rs

1// src/modules/system_monitor.rs - OPTIMIZED VERSION
2use std::fmt::Write as FmtWrite;
3use std::thread;
4use std::time::Duration;
5
6use sysinfo::System;
7
8use crate::modules::system_info::{SystemInfo, SystemReport};
9
10#[derive(Debug, Clone, Copy)]
11pub enum ViewType {
12    Stats,
13    Monitor,
14    Live,
15}
16
17impl std::fmt::Display for ViewType {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        match self {
20            ViewType::Stats => write!(f, "Stats"),
21            ViewType::Monitor => write!(f, "Monitor"),
22            ViewType::Live => write!(f, "Live"),
23        }
24    }
25}
26
27struct StringBuffer {
28    buffer: String,
29}
30
31impl StringBuffer {
32    fn new() -> Self {
33        Self {
34            buffer: String::with_capacity(4096),
35        }
36    }
37
38    fn clear(&mut self) {
39        self.buffer.clear();
40    }
41
42    fn write_str(&mut self, s: &str) {
43        self.buffer.push_str(s);
44    }
45
46    fn write_fmt(&mut self, args: std::fmt::Arguments<'_>) {
47        let _ = self.buffer.write_fmt(args);
48    }
49
50    fn to_lines(&self) -> Vec<String> {
51        self.buffer.lines().map(String::from).collect()
52    }
53}
54
55pub struct SystemMonitor {
56    pub view: ViewType,
57    pub suggestion: bool,
58    pub verbose: bool,
59    pub left: Vec<String>,
60    pub right: Vec<String>,
61    sys: System,
62    left_buffer: StringBuffer,
63    right_buffer: StringBuffer,
64}
65
66impl SystemMonitor {
67    pub fn new(view: ViewType, suggestion: bool) -> Self {
68        Self::new_with_verbose(view, suggestion, false)
69    }
70
71    pub fn new_with_verbose(view: ViewType, suggestion: bool, verbose: bool) -> Self {
72        let sys = System::new_all();
73
74        Self {
75            view,
76            suggestion,
77            verbose,
78            left: Vec::new(),
79            right: Vec::new(),
80            sys,
81            left_buffer: StringBuffer::new(),
82            right_buffer: StringBuffer::new(),
83        }
84    }
85
86    pub fn update(&mut self) {
87        self.sys.refresh_cpu_all();
88        std::thread::sleep(Duration::from_millis(200));
89        self.sys.refresh_cpu_all();
90
91        let sys_info = SystemInfo::new();
92        let report = sys_info.generate_system_report(&self.sys);
93        self.format_system_info(&report);
94    }
95
96    fn format_option<T: std::fmt::Display + std::fmt::Debug>(
97        opt: Option<T>,
98        verbose: bool,
99    ) -> String {
100        if verbose {
101            format!("{:?}", opt)
102        } else {
103            opt.map(|v| v.to_string())
104                .unwrap_or_else(|| "Unknown".to_string())
105        }
106    }
107
108    fn format_battery_status(
109        is_charging: Option<bool>,
110        is_ac_plugged: Option<bool>,
111        verbose: bool,
112    ) -> String {
113        if verbose {
114            format!(
115                "is_charging: {:?}, is_ac_plugged: {:?}",
116                is_charging, is_ac_plugged
117            )
118        } else {
119            match (is_charging, is_ac_plugged) {
120                (Some(true), _) => "Charging".to_string(),
121                (Some(false), Some(false)) => "Discharging".to_string(),
122                (Some(false), Some(true)) => "Charged".to_string(),
123                _ => "Unknown".to_string(),
124            }
125        }
126    }
127
128    pub fn format_system_info(&mut self, report: &SystemReport) {
129        self.left_buffer.clear();
130        self.right_buffer.clear();
131
132        self.format_left_column(report);
133        self.format_right_column(report);
134
135        self.left = self.left_buffer.to_lines();
136        self.right = self.right_buffer.to_lines();
137    }
138
139    fn format_left_column(&mut self, report: &SystemReport) {
140        let buf = &mut self.left_buffer;
141
142        buf.write_str("System Information\n\n");
143        buf.write_fmt(format_args!(
144            "Linux distro: {} {}\n",
145            report.distro_name, report.distro_ver
146        ));
147        buf.write_fmt(format_args!("Linux kernel: {}\n", report.kernel_version));
148        buf.write_fmt(format_args!("Processor: {}\n", report.processor_model));
149
150        if self.verbose {
151            buf.write_fmt(format_args!("Cores: {:?}\n", report.total_core));
152            buf.write_fmt(format_args!("Driver: {:?}\n", report.cpu_driver));
153        } else {
154            buf.write_fmt(format_args!(
155                "Cores: {}\n",
156                Self::format_option(report.total_core, false)
157            ));
158            buf.write_fmt(format_args!(
159                "Driver: {}\n",
160                report.cpu_driver.as_deref().unwrap_or("Unknown")
161            ));
162        }
163
164        buf.write_fmt(format_args!("Architecture: {}\n\n", report.arch));
165
166        if crate::CONFIG.has_config() {
167            buf.write_fmt(format_args!(
168                "Using settings defined in {}\n\n",
169                crate::CONFIG.get_path().display()
170            ));
171        }
172
173        buf.write_str("Current CPU Stats\n\n");
174
175        if self.verbose {
176            buf.write_fmt(format_args!(
177                "CPU max frequency: {:?} MHz\n",
178                report.cpu_max_freq
179            ));
180            buf.write_fmt(format_args!(
181                "CPU min frequency: {:?} MHz\n\n",
182                report.cpu_min_freq
183            ));
184        } else {
185            let max_freq = report
186                .cpu_max_freq
187                .map(|f| format!("{:.0}", f))
188                .unwrap_or_else(|| "Unknown".to_string());
189            let min_freq = report
190                .cpu_min_freq
191                .map(|f| format!("{:.0}", f))
192                .unwrap_or_else(|| "Unknown".to_string());
193            buf.write_fmt(format_args!("CPU max frequency: {} MHz\n", max_freq));
194            buf.write_fmt(format_args!("CPU min frequency: {} MHz\n\n", min_freq));
195        }
196
197        buf.write_fmt(format_args!(
198            "{:<5} {:<7} {:<11} {:<8}\n",
199            "Core", "Usage", "Temp", "Freq"
200        ));
201
202        for core in &report.cores_info {
203            let temp_str = if core.temperature > 0.0 {
204                format!("{:.0}°C", core.temperature)
205            } else {
206                "--°C".to_string()
207            };
208
209            buf.write_fmt(format_args!(
210                "{:<5} {:>6.1}% {:<11} {:>5.0} MHz\n",
211                format!("CPU{}", core.id),
212                core.usage,
213                temp_str,
214                core.frequency
215            ));
216        }
217
218        if let Some(fan) = report.cpu_fan_speed {
219            buf.write_str("\n");
220            buf.write_fmt(format_args!("CPU fan speed: {} RPM\n", fan));
221        }
222    }
223
224    fn format_right_column(&mut self, report: &SystemReport) {
225        let buf = &mut self.right_buffer;
226
227        buf.write_str("Battery Stats\n\n");
228
229        if self.verbose {
230            buf.write_fmt(format_args!("Battery info: {:?}\n\n", report.battery_info));
231        } else {
232            let battery_status = Self::format_battery_status(
233                report.battery_info.is_charging,
234                report.battery_info.is_ac_plugged,
235                false,
236            );
237            buf.write_fmt(format_args!("Battery status: {}\n", battery_status));
238
239            let battery_level = report
240                .battery_info
241                .battery_level
242                .map(|b| format!("{}%", b))
243                .unwrap_or_else(|| "Unknown".to_string());
244            buf.write_fmt(format_args!("Battery level: {}\n", battery_level));
245
246            let ac_status = report
247                .battery_info
248                .is_ac_plugged
249                .map(|ac| if ac { "Yes" } else { "No" })
250                .unwrap_or("Unknown");
251            buf.write_fmt(format_args!("AC plugged: {}\n", ac_status));
252
253            let start_threshold = report
254                .battery_info
255                .charging_start_threshold
256                .map(|t| format!("{}%", t))
257                .unwrap_or_else(|| "Not set".to_string());
258            buf.write_fmt(format_args!("Start threshold: {}\n", start_threshold));
259
260            let stop_threshold = report
261                .battery_info
262                .charging_stop_threshold
263                .map(|t| format!("{}%", t))
264                .unwrap_or_else(|| "Not set".to_string());
265            buf.write_fmt(format_args!("Stop threshold: {}\n\n", stop_threshold));
266        }
267
268        buf.write_str("CPU Frequency Scaling\n\n");
269
270        if self.verbose {
271            buf.write_fmt(format_args!("Current governor: {:?}\n", report.current_gov));
272            buf.write_fmt(format_args!("EPP: {:?}\n", report.current_epp));
273            buf.write_fmt(format_args!("EPB: {:?}\n", report.current_epb));
274        } else {
275            let current_gov = report.current_gov.as_deref().unwrap_or("Unknown");
276            buf.write_fmt(format_args!("Current governor: {}\n", current_gov));
277
278            if let Some(epp) = &report.current_epp {
279                buf.write_fmt(format_args!("EPP: {}\n", epp));
280            } else {
281                buf.write_str("EPP: Not supported\n");
282            }
283
284            if let Some(epb) = &report.current_epb {
285                buf.write_fmt(format_args!("EPB: {}\n", epb));
286            }
287        }
288
289        if self.suggestion {
290            if let Some(sugg) = SystemInfo::governor_suggestion() {
291                if report.current_gov.as_deref() != Some(&sugg) {
292                    buf.write_fmt(format_args!("Suggested governor: {}\n", sugg));
293                }
294            }
295        }
296
297        buf.write_str("\n");
298
299        buf.write_str("System Statistics\n\n");
300        buf.write_fmt(format_args!("CPU usage: {:.1}%\n", report.cpu_usage));
301        buf.write_fmt(format_args!("System load: {:.2}\n", report.load));
302
303        if !report.cores_info.is_empty() {
304            let avg_temp: f32 = report
305                .cores_info
306                .iter()
307                .map(|c| c.temperature)
308                .filter(|&t| t > 0.0)
309                .sum::<f32>();
310            let temp_count = report
311                .cores_info
312                .iter()
313                .filter(|c| c.temperature > 0.0)
314                .count();
315
316            if temp_count > 0 {
317                let avg_temp = avg_temp / temp_count as f32;
318                buf.write_fmt(format_args!("Average temp: {:.1} °C\n", avg_temp));
319            }
320        }
321
322        if let Some((a, b, c)) = report.avg_load {
323            let load_status = if report.load < 1.0 { "optimal" } else { "high" };
324            buf.write_fmt(format_args!(
325                "Load {}: {:.2}, {:.2}, {:.2}\n",
326                load_status, a, b, c
327            ));
328        }
329
330        if self.verbose {
331            buf.write_fmt(format_args!("Turbo boost: {:?}\n", report.is_turbo_on));
332        } else {
333            let turbo_status = match (report.is_turbo_on.0, report.is_turbo_on.1) {
334                (Some(on), _) => if on { "On" } else { "Off" }.to_string(),
335                (None, Some(auto)) => {
336                    format!("Auto ({})", if auto { "enabled" } else { "disabled" })
337                }
338                _ => "Unknown".to_string(),
339            };
340            buf.write_fmt(format_args!("Turbo boost: {}\n", turbo_status));
341        }
342
343        if self.suggestion {
344            if let Some(on) = report.is_turbo_on.0 {
345                let sugg = SystemInfo::turbo_on_suggestion(&self.sys);
346                if sugg != on {
347                    buf.write_fmt(format_args!(
348                        "Suggested turbo: {}\n",
349                        if sugg { "On" } else { "Off" }
350                    ));
351                }
352            }
353        }
354    }
355
356    pub fn run_blocking(&mut self) {
357        loop {
358            self.update();
359
360            print!("\x1B[2J\x1B[1;1H");
361
362            let width = 100usize;
363            let half = width / 2 - 2;
364            let rows = std::cmp::max(self.left.len(), self.right.len());
365
366            for i in 0..rows {
367                let left = self.left.get(i).map(String::as_str).unwrap_or("");
368                let right = self.right.get(i).map(String::as_str).unwrap_or("");
369
370                if left.len() > half {
371                    let truncate_at = half.saturating_sub(3);
372                    println!(
373                        "{:<half$}... │ {}",
374                        &left[..truncate_at],
375                        right,
376                        half = half
377                    );
378                } else {
379                    println!("{:<half$} │ {}", left, right, half = half);
380                }
381            }
382
383            thread::sleep(Duration::from_secs(2));
384        }
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    #[test]
393    fn test_string_buffer() {
394        let mut buf = StringBuffer::new();
395        buf.write_str("Hello\n");
396        buf.write_fmt(format_args!("World {}\n", 123));
397        let lines = buf.to_lines();
398        assert_eq!(lines.len(), 2);
399        assert_eq!(lines[0], "Hello");
400        assert_eq!(lines[1], "World 123");
401    }
402
403    #[test]
404    fn test_monitor_update() {
405        let mut monitor = SystemMonitor::new(ViewType::Monitor, false);
406        monitor.update();
407        assert!(!monitor.left.is_empty());
408        assert!(!monitor.right.is_empty());
409    }
410}