Compare commits

...

9 Commits

9 changed files with 300 additions and 12 deletions

142
Cargo.lock generated
View File

@ -51,6 +51,56 @@ dependencies = [
"equator",
]
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.102"
@ -216,6 +266,46 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "cmake"
version = "0.1.58"
@ -231,6 +321,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "core2"
version = "0.4.0"
@ -528,6 +624,12 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "image"
version = "0.25.10"
@ -609,6 +711,12 @@ dependencies = [
"syn",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
version = "0.14.0"
@ -877,6 +985,12 @@ version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "owned_ttf_parser"
version = "0.25.1"
@ -1300,6 +1414,12 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.117"
@ -1412,6 +1532,12 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "v_frame"
version = "0.3.9"
@ -1487,6 +1613,7 @@ dependencies = [
name = "waybar-collectd"
version = "0.1.0"
dependencies = [
"clap",
"draw",
"imageproc",
"protocol",
@ -1511,6 +1638,21 @@ dependencies = [
"safe_arch",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "winnow"
version = "1.0.3"

86
README.md Normal file
View File

@ -0,0 +1,86 @@
# waybar-collectd
A Waybar graphing utility inspired by the Gnome 2 Panel system indicator. It uses collectd's `unixsock` plugin to gather metrics and renders images for the `waybar-image` plugin.
## Installation
```bash
cargo install --git https://forgejo.cerno.ch/handy/waybar-collectd.git
```
## Configuring collectd
Install and configure `collectd` with the `unixsock` plugin. For per-second updates, adjust the sampling interval. Ensure `collectd` is enabled to start on boot.
**Example config (`/etc/collectd.d/waybar.conf`):**
```conf
Interval 1 # Sample data every 1 second
LoadPlugin unixsock
<Plugin unixsock>
SocketPerms "0666"
</Plugin>
# Enable the plugins you want to use
LoadPlugin cpu
LoadPlugin memory
LoadPlugin disk
LoadPlugin network
```
```bash
sudo systemctl enable --now collectd
```
## Configuring waybar-collectd
Define your graphs and data sources in `config.toml`. To ensure the daemon runs on login, use a user systemd unit or your WM configuration.
### User systemd unit example
`~/.config/systemd/user/waybar-hwmonitor-daemon.service`
```ini
[Unit]
Description=Data collection daemon for waybar hw monitor
[Service]
Type=simple
ExecStart=%h/.cargo/bin/waybar-collectd --config %h/.config/waybar/modules/waybar-collectd-config.toml
Restart=on-failure
RestartSec=2s
[Install]
WantedBy=default.target
```
## Configuring Waybar
Add a module for each graph to your Waybar config. Ensure the `size` matches the rendered image dimensions.
**Example config (`~/.config/waybar/config`):**
```json
[
{
"layer": "bottom",
"position": "top",
"modules-center": ["image#net", "image#disk", "image#mem", "image#cpu"],
"height": 24,
"image#cpu": {
"path": "/tmp/waybar-graphs/cpu.png",
"size": 120,
"interval": 1
},
"image#mem": {
"path": "/tmp/waybar-graphs/mem.png",
"size": 120,
"interval": 1
},
"image#disk": {
"path": "/tmp/waybar-graphs/disk.png",
"size": 120,
"interval": 1
},
"image#net": {
"path": "/tmp/waybar-graphs/net.png",
"size": 120,
"interval": 1
}
}
]
```

View File

@ -1,6 +1,10 @@
[global]
period = 1
socket = "/var/run/collectd-unixsock"
[[graph]]
name = "cpu load"
path = "cpu.png"
path = "/tmp/waybar-graphs/cpu.png"
width_px = 480
height_px = 98
samples = 60
@ -35,7 +39,7 @@ metrics = [
[[graph]]
name = "memory"
path = "mem.png"
path = "/tmp/waybar-graphs/mem.png"
width_px = 480
height_px = 98
samples = 90
@ -64,7 +68,7 @@ metrics = [
[[graph]]
name = "disk"
path = "disk.png"
path = "/tmp/waybar-graphs/disk.png"
width_px = 480
height_px = 98
samples = 90
@ -85,7 +89,7 @@ metrics = [
[[graph]]
name = "network"
path = "net.png"
path = "/tmp/waybar-graphs/net.png"
width_px = 480
height_px = 98
samples = 90

View File

@ -12,10 +12,11 @@ impl Container {
}
pub fn push(&mut self, data: Vec<f32>) {
self.data.push(data);
if self.data.len() as u32 > self.max_samples {
self.data.pop();
self.data.remove(0);
}
self.data.push(data)
}
pub fn iter(&self) -> std::slice::Iter<'_, Vec<f32>> {

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.6.1", features = ["derive"] }
draw = { version = "0.1.0", path = "../draw" }
imageproc = { version = "0.26.1", features = ["display-window"] }
protocol = { path = "../protocol" }

View File

@ -4,9 +4,16 @@ use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Config {
pub global: Global,
pub graph: Vec<GraphConfig>,
}
#[derive(Debug, Deserialize)]
pub struct Global {
pub period: f32,
pub socket: String,
}
#[derive(Debug, Deserialize)]
pub struct GraphConfig {
pub name: String,

View File

@ -8,24 +8,32 @@ use protocol::Protocol;
use std::{thread, time};
use crate::runner::{Graph, Runner};
use clap::Parser;
#[derive(Parser)]
struct Cli {
/// Config file path
#[arg(short = 'c', long = "config")]
config: String,
}
fn main() {
if let Ok(mut proto) = Protocol::new("/var/run/collectd-unixsock") {
let config = parse("config.toml");
let args = Cli::parse();
let config = parse(&args.config);
if let Ok(mut proto) = Protocol::new(&config.global.socket) {
let graphs = config
.graph
.iter()
.map(|g| Graph::new(g, &mut proto))
.collect();
let mut runner = Runner::new(graphs, proto);
let mut runner = Runner::new(graphs, proto, config.global.period);
loop {
let r = runner.tick();
if let Err(e) = r {
println!("{}", e.to_string());
}
thread::sleep(time::Duration::from_secs(1));
thread::sleep(time::Duration::from_secs_f32(config.global.period));
}
} else {
panic!("sock not found!");

View File

@ -85,4 +85,8 @@ impl ObservableCollection {
pub fn sample(&self, proto: &mut Protocol) -> Result<Vec<f32>, ProtocolError> {
self.observables.iter().map(|o| o.sample(proto)).collect()
}
pub fn get_num_observables(&self) -> usize {
self.observables.len()
}
}

View File

@ -1,6 +1,8 @@
use core::fmt;
use std::{fs, time::SystemTime};
use draw::{StackedConfig, StackedSeries, StackedSeriesConfig, stacked};
use imageproc::image::codecs::pnm::SampleEncoding;
use protocol::Protocol;
use crate::{
@ -68,17 +70,50 @@ impl fmt::Display for RunnerError {
pub struct Runner {
graphs: Vec<Graph>,
proto: Protocol,
last_sample: SystemTime,
sampling_period: f32,
}
impl Runner {
pub fn new(graphs: Vec<Graph>, proto: Protocol) -> Runner {
pub fn new(graphs: Vec<Graph>, proto: Protocol, sampling_period: f32) -> Runner {
Runner {
graphs: graphs,
proto: proto,
last_sample: SystemTime::UNIX_EPOCH,
sampling_period: sampling_period,
}
}
pub fn tick(&mut self) -> Result<(), RunnerError> {
let now = SystemTime::now();
let dt = if self.last_sample == SystemTime::UNIX_EPOCH {
self.sampling_period
} else {
now.duration_since(self.last_sample)
.expect("time jumped back")
.as_secs_f32()
};
self.last_sample = now;
for g in &mut self.graphs {
let path = std::path::Path::new(&g.series.config.path)
.parent()
.expect("wrong target directory");
if !fs::exists(path).unwrap_or(false) {
fs::create_dir(path).map_err(|_| {
RunnerError::CouldNotSaveImage("cannot create parent directory".to_string())
})?;
}
// fill in empty samples for missing data
if dt >= 2. {
let dt_round = dt.round() as u32;
for _ in 0..dt_round {
let dummy = vec![0.; g.observable.get_num_observables()];
g.series.push(dummy)
}
}
// sample data
let sample = g
.observable
.sample(&mut self.proto)