lazybar_core/panels/
cpu.rs1use 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))]
33pub 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 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}