rs_blocks/blocks/
cpu.rs

1// Copyright ⓒ 2019-2021 Lewis Belcher
2// Licensed under the MIT license (see LICENSE or <http://opensource.org/licenses/MIT>).
3// All files in the project carrying such notice may not be copied, modified, or
4// distributed except according to those terms
5
6//! # CPU block
7//!
8//! Use this block to get CPU monitoring in the status bar.
9//!
10//! This block reads from `/proc/stat` to calculate CPU usage.
11//!
12//! Typical configuration:
13//!
14//! ```toml
15//! [cpu]
16//! ```
17//!
18//! ## Configuration options
19//!
20//! - `name`: Name of the block (must be unique)
21//! - `period`: Default update period in seconds (extra updates may occur on
22//!    event changes etc)
23//! - `alpha`: Weight for the exponential moving average of value updates
24
25use crate::blocks::{Block, Configure, Message, Sender};
26use crate::{ema, utils};
27use regex::Regex;
28use serde::Deserialize;
29use std::thread;
30
31const PATTERN: &str = r"cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)";
32const PATH: &str = "/proc/stat";
33
34#[derive(Configure, Deserialize)]
35pub struct Cpu {
36	#[serde(default = "default_name")]
37	name: String,
38	#[serde(default = "default_period")]
39	period: f32,
40	#[serde(default = "default_alpha")]
41	alpha: f32,
42}
43
44fn default_name() -> String {
45	"cpu".to_string()
46}
47
48fn default_period() -> f32 {
49	1.0
50}
51
52fn default_alpha() -> f32 {
53	0.7
54}
55
56impl Sender for Cpu {
57	fn add_sender(&self, channel: crossbeam_channel::Sender<Message>) -> anyhow::Result<()> {
58		let name = self.get_name();
59		let monitor = utils::monitor_file(PATH.to_string(), self.period);
60		let mut perc = ema::Ema::new(self.alpha);
61		let mut cpu = Usage {
62			idle: 0.0,
63			total: 0.0,
64		};
65		let mut block = Block::new(name.clone(), true);
66
67		thread::spawn(move || {
68			for c in monitor {
69				let current_cpu = calc_cpu(match_proc(&c));
70				block.full_text = Some(format!(
71					" {:.1}%",
72					perc.push(calc_dcpu(&current_cpu, &cpu))
73				));
74				channel.send((name.clone(), block.to_string())).unwrap();
75				cpu = current_cpu;
76			}
77		});
78
79		Ok(())
80	}
81}
82
83struct Usage {
84	idle: f32,
85	total: f32,
86}
87
88fn calc_cpu(stat: regex::Captures) -> Usage {
89	// (user, nice, system, idle, iowait, irq, softirq)
90	let stats: Vec<f32> = stat
91		.iter()
92		.skip(1)
93		.map(|x| x.unwrap().as_str().parse().unwrap())
94		.collect();
95
96	Usage {
97		idle: (stats[3] + stats[4]),
98		total: stats.iter().sum(),
99	}
100}
101
102fn calc_dcpu(cpu: &Usage, prevcpu: &Usage) -> f32 {
103	(1.0 - (cpu.idle - prevcpu.idle) / (cpu.total - prevcpu.total)) * 100.0
104}
105
106fn match_proc(s: &str) -> regex::Captures {
107	lazy_static! {
108		static ref RE: Regex = Regex::new(PATTERN).unwrap();
109	}
110	RE.captures(s)
111		.expect(&format!("Failed to match /proc/stat contents '{}'", s))
112}
113
114#[cfg(test)]
115mod tests {
116	use super::*;
117	const STATFILE: &str = "cpu  237476 0 85111 17267319 2310 34402 4846 0 0 0\n";
118
119	#[test]
120	fn regex_matches() {
121		match_proc(&STATFILE);
122	}
123}