lazybar_core/panels/
cpu.rs

1use std::{
2    collections::HashMap,
3    fs::File,
4    io::Read,
5    rc::Rc,
6    sync::{Arc, Mutex},
7    time::Duration,
8};
9
10use anyhow::{anyhow, Result};
11use async_trait::async_trait;
12use derive_builder::Builder;
13use futures::task::AtomicWaker;
14use lazy_static::lazy_static;
15use regex::Regex;
16use tokio_stream::StreamExt;
17
18use crate::{
19    bar::PanelDrawInfo,
20    common::{PanelCommon, ShowHide},
21    remove_string_from_config, remove_uint_from_config, Attrs, Highlight,
22    ManagedIntervalStream, PanelConfig, PanelRunResult, Ramp,
23};
24
25lazy_static! {
26    static ref REGEX: Regex =
27        Regex::new(r"cpu\s*(?<user>\d+) (?<nice>\d+) (?<system>\d+) (?<idle>\d+) \d+ \d+ \d+ (?<steal>\d+)").unwrap();
28}
29
30#[derive(Debug, Clone, Builder)]
31#[builder_struct_attr(allow(missing_docs))]
32#[builder_impl_attr(allow(missing_docs))]
33/// Display information about CPU usage based on `/proc/stat`
34pub struct Cpu {
35    name: &'static str,
36    #[builder(default = "Duration::from_secs(10)")]
37    interval: Duration,
38    #[builder(default)]
39    waker: Arc<AtomicWaker>,
40    #[builder(default = r#"String::from("/proc/stat")"#)]
41    path: String,
42    last_load: Load,
43    format: &'static str,
44    attrs: Attrs,
45    #[builder(default, setter(strip_option))]
46    highlight: Option<Highlight>,
47    ramp: Ramp,
48    common: PanelCommon,
49}
50
51impl Cpu {
52    fn draw(
53        &mut self,
54        cr: &Rc<cairo::Context>,
55        height: i32,
56        paused: Arc<Mutex<bool>>,
57    ) -> Result<PanelDrawInfo> {
58        let load = read_current_load(self.path.as_str())?;
59
60        let diff = load.total - self.last_load.total;
61
62        let percentage = (diff - (load.idle - self.last_load.idle)) as f64
63            / diff as f64
64            * 100.0;
65
66        let text = self
67            .format
68            .replace("%percentage%", format!("{percentage:.0}").as_str())
69            .replace(
70                "%ramp%",
71                self.ramp.choose(percentage, 0.0, 100.0).as_str(),
72            );
73
74        self.last_load = load;
75
76        self.common.draw(
77            cr,
78            text.as_str(),
79            &self.attrs,
80            self.common.dependence,
81            self.highlight.clone(),
82            self.common.images.clone(),
83            height,
84            ShowHide::Default(paused, self.waker.clone()),
85            format!("{self:?}"),
86        )
87    }
88}
89
90#[async_trait(?Send)]
91impl PanelConfig for Cpu {
92    /// Parses an instance of the panel from the global [`Config`]
93    ///
94    /// Configuration options:
95    /// - `interval`: how long to wait in seconds between each check
96    ///   - type: u64
97    ///   - default: 10
98    /// - `path`: the file path to check
99    ///   - type: String
100    ///   - default: `/proc/stat` - If you're considering changing this, you
101    ///     might want to use a different panel like
102    ///     [`Inotify`][crate::panels::Inotify]
103    /// - `format`: the format string
104    ///   - type: String
105    ///   - default: `CPU: %percentage%%`
106    ///   - formatting options: `%percentage%`
107    /// - `attrs`: A string specifying the attrs for the panel. See
108    ///   [`Attrs::parse`] for details.
109    /// - `highlight`: A string specifying the highlight for the panel. See
110    ///   [`Highlight::parse`] for details.
111    /// - `ramp`: A string specifying the ramp to show CPU usage. See
112    ///   [`Ramp::parse`] for details.
113    /// - See [`PanelCommon::parse_common`].
114    fn parse(
115        name: &'static str,
116        table: &mut HashMap<String, config::Value>,
117        _global: &config::Config,
118    ) -> Result<Self> {
119        let mut builder = CpuBuilder::default();
120
121        builder.name(name);
122        if let Some(interval) = remove_uint_from_config("interval", table) {
123            builder.interval(Duration::from_secs(interval));
124        }
125        if let Some(path) = remove_string_from_config("path", table) {
126            builder.last_load(read_current_load(path.as_str())?);
127            builder.path(path);
128        } else {
129            builder.last_load(read_current_load("/proc/stat")?);
130        }
131        let common = PanelCommon::parse_common(table)?;
132        let format = PanelCommon::parse_format(table, "", "CPU: %percentage%%");
133        let attr = PanelCommon::parse_attr(table, "");
134        let ramp = PanelCommon::parse_ramp(table, "");
135        builder.common(common);
136        builder.format(format.leak());
137        builder.attrs(attr);
138        builder.highlight(PanelCommon::parse_highlight(table, ""));
139        builder.ramp(ramp);
140
141        Ok(builder.build()?)
142    }
143
144    fn props(&self) -> (&'static str, bool) {
145        (self.name, self.common.visible)
146    }
147
148    async fn run(
149        mut self: Box<Self>,
150        cr: Rc<cairo::Context>,
151        global_attrs: Attrs,
152        height: i32,
153    ) -> PanelRunResult {
154        self.attrs.apply_to(&global_attrs);
155
156        let paused = Arc::new(Mutex::new(false));
157
158        let stream = ManagedIntervalStream::builder()
159            .duration(self.interval)
160            .paused(paused.clone())
161            .build()?
162            .map(move |_| self.draw(&cr, height, paused.clone()));
163
164        Ok((Box::pin(stream), None))
165    }
166}
167
168#[derive(Debug, Clone, Copy)]
169struct Load {
170    idle: u64,
171    total: u64,
172}
173
174fn read_current_load(path: &str) -> Result<Load> {
175    let mut stat = String::new();
176    File::open(path)?.read_to_string(&mut stat)?;
177
178    let (_, [user, nice, system, idle, steal]) = REGEX
179        .captures(stat.as_str())
180        .ok_or_else(|| {
181            anyhow!("Failed to read CPU information from {:?}", path)
182        })?
183        .extract();
184
185    let user = user.parse::<u64>()?;
186    let nice = nice.parse::<u64>()?;
187    let system = system.parse::<u64>()?;
188    let idle = idle.parse::<u64>()?;
189    let steal = steal.parse::<u64>()?;
190
191    let total = user + nice + system + idle + steal;
192
193    Ok(Load { idle, total })
194}