// MIT License // Copyright (c) 2025 Mahesh @ HeshApps // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //! CPU usage monitoring module for Unibar //! //! This module tracks CPU utilization by sampling /proc/stat and calculating //! the percentage of time spent in different CPU states. It provides real-time //! CPU usage information for display in the status bar. use std::fs::File; use std::io::{self, BufRead, BufReader}; use std::thread; use std::time::Duration; use crate::common; use crate::bar_modules; /// Represents CPU time spent in various states /// /// This struct holds counters for different CPU states as reported by /proc/stat. /// Each field represents the amount of time the CPU has spent in that particular state /// since system boot, measured in USER_HZ units (typically 100Hz). #[derive(Debug, Default, Clone)] struct CpuTimes { user: u64, nice: u64, system: u64, idle: u64, iowait: u64, irq: u64, softirq: u64, steal: u64, guest: u64, guest_nice: u64, } impl CpuTimes { pub fn new() -> Self { CpuTimes { user: 0, nice: 0, system: 0, idle: 0, iowait: 0, irq: 0, softirq: 0, steal: 0, guest: 0, guest_nice: 0 } } fn total(&self) -> u64 { self.user + self.nice + self.system + self.idle + self.iowait + self.irq + self.softirq + self.steal + self.guest + self.guest_nice } fn idle(&self) -> u64 { self.idle + self.iowait } } #[derive(Clone)] pub struct UnibarModuleCpu { opts: common::AppOptions, cpu_times_sample_1: CpuTimes, cpu_times_sample_2: CpuTimes, } impl UnibarModuleCpu { // -------------------- pub fn new(o :common::AppOptions) -> Self { UnibarModuleCpu { opts: o, cpu_times_sample_1: CpuTimes::new(), cpu_times_sample_2: CpuTimes::new(), } } // -------------------- // cpu 242109 7 60985 6538626 8344 39138 9647 0 0 0 // * user - time spent in user mode // * nice - time spent processing nice processes in user mode // * system - time spent executing kernel code // * idle - time spent idle // * iowait - time spent waiting for I/O // * irq - time spent servicing interrupts // * softirq - time spent servicing software interrupts // * steal - time stolen from a virtual machine // * guest - time spent running a virtual CPU for a guest operating system // * guest_nice - time spent running a virtual CPU for a “niced” guest operating system fn read_cpu_times(&self) -> io::Result { let file = File::open("/proc/stat")?; let reader = BufReader::new(file); for line in reader.lines() { let line = line?; if line.starts_with("cpu ") { let fields: Vec = line.split_whitespace().skip(1).map(|s| s.parse().unwrap_or(0)).collect(); if fields.len() >= 8 { return Ok(CpuTimes { user: fields[0], nice: fields[1], system: fields[2], idle: fields[3], iowait: fields[4], irq: fields[5], softirq: fields[6], steal: fields[7], guest: if fields.len() > 8 { fields[8] } else { 0 }, guest_nice: if fields.len() > 9 { fields[9] } else { 0 }, }); } } } Err(io::Error::new(io::ErrorKind::InvalidData, "Could not find cpu line in /proc/stat")) } } impl bar_modules::BarModuleActions for UnibarModuleCpu { // -------------------- fn get_name(&self) -> String { return "UnibarModuleCpu".to_string(); } // -------------------- fn clear(&mut self) { self.cpu_times_sample_1 = CpuTimes::new(); self.cpu_times_sample_2 = CpuTimes::new(); } // -------------------- fn generate_data(&mut self) { self.cpu_times_sample_1 = self.read_cpu_times().expect("Trouble getting CPU times sample 1"); thread::sleep(Duration::from_millis(500)); self.cpu_times_sample_2 = self.read_cpu_times().expect("Trouble getting CPU times sample 2"); if self.opts.debug { println!("-----> CPU times sample 1 - {:#?}", self.cpu_times_sample_1); println!("-----> CPU times sample 2 - {:#?}", self.cpu_times_sample_2); } } // -------------------- fn get_content(&self) -> String { let sample_1_total = self.cpu_times_sample_1.total(); let sample_2_total = self.cpu_times_sample_2.total(); let sample_1_idle = self.cpu_times_sample_1.idle(); let sample_2_idle = self.cpu_times_sample_2.idle(); let total_diff = sample_2_total - sample_1_total; let idle_diff = sample_2_idle - sample_1_idle; if total_diff > 0 { let cpu_usage = (1.0 - (idle_diff as f64 / total_diff as f64)) * 100.0; return format!("{}%", cpu_usage.ceil() as i32); } else { return format!("CPU?"); } } // -------------------- fn get_icon(&self) -> String { return "💻".to_string(); } } impl bar_modules::BarModuleDebug for UnibarModuleCpu { // -------------------- fn post_debug(&self) { } }