koyori/src/cpu.rs

199 lines
8.0 KiB
Rust

use anyhow::{Context, Result};
use async_trait::async_trait;
use prometheus_client::encoding::text::Encode;
use prometheus_client::metrics::family::Family;
use prometheus_client::registry::Registry;
use serde::Deserialize;
use tokio::process::Command;
use tracing::trace;
use crate::{F64Gauge, Metrics, HOSTNAME};
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
struct Label {
hostname: &'static str,
cpu: String,
}
#[derive(Default)]
pub struct Exporter {
usr: Family<Label, F64Gauge>,
nice: Family<Label, F64Gauge>,
sys: Family<Label, F64Gauge>,
iowait: Family<Label, F64Gauge>,
irq: Family<Label, F64Gauge>,
soft: Family<Label, F64Gauge>,
steal: Family<Label, F64Gauge>,
guest: Family<Label, F64Gauge>,
gnice: Family<Label, F64Gauge>,
idle: Family<Label, F64Gauge>,
}
#[async_trait]
impl Metrics for Exporter {
fn prefix(&self) -> &'static str {
"cpu"
}
async fn should_collect(&self) -> Result<()> {
Command::new("mpstat")
.output()
.await
.context("While checking mpstat")
.map(drop)
}
fn register(&self, sub_registry: &mut Registry) -> Result<()> {
sub_registry.register("usr", "usr time (mpstat)", Box::new(self.usr.clone()));
sub_registry.register("nice", "nice time (mpstat)", Box::new(self.nice.clone()));
sub_registry.register("sys", "sys time (mpstat)", Box::new(self.sys.clone()));
sub_registry.register(
"iowait",
"iowait time (mpstat)",
Box::new(self.iowait.clone()),
);
sub_registry.register("irq", "irq time (mpstat)", Box::new(self.irq.clone()));
sub_registry.register("soft", "soft time (mpstat)", Box::new(self.soft.clone()));
sub_registry.register("steal", "steal time (mpstat)", Box::new(self.steal.clone()));
sub_registry.register("guest", "guest time (mpstat)", Box::new(self.guest.clone()));
sub_registry.register(
"gnice",
"guest nice time (mpstat)",
Box::new(self.gnice.clone()),
);
sub_registry.register("idle ", "idle time (mpstat)", Box::new(self.idle.clone()));
Ok(())
}
async fn collect(&self) -> Result<()> {
trace!("Started");
let json = Command::new("mpstat")
.args(["-o", "JSON", "-P", "ALL", "5", "1"])
.output()
.await
.context("While collecting mpstat")?
.stdout;
let data: MpStatOutput = serde_json::from_slice(&json)?;
let statistics = data
.sysstat
.hosts
.into_iter()
.next()
.context("Getting the first host")?
.statistics;
let cpus = statistics
.into_iter()
.next()
.context("getting first stat measurement")?
.cpu_load;
for cpu in cpus {
let label = Label {
hostname: &HOSTNAME,
cpu: cpu.identifier,
};
self.usr.get_or_create(&label).set(cpu.usr);
self.nice.get_or_create(&label).set(cpu.nice);
self.sys.get_or_create(&label).set(cpu.sys);
self.iowait.get_or_create(&label).set(cpu.iowait);
self.irq.get_or_create(&label).set(cpu.irq);
self.soft.get_or_create(&label).set(cpu.soft);
self.steal.get_or_create(&label).set(cpu.steal);
self.guest.get_or_create(&label).set(cpu.guest);
self.gnice.get_or_create(&label).set(cpu.gnice);
self.idle.get_or_create(&label).set(cpu.idle);
}
trace!("Done");
Ok(())
}
}
#[derive(Deserialize)]
struct MpStatOutput {
sysstat: SysStat,
}
#[derive(Deserialize)]
struct SysStat {
hosts: Vec<Host>,
}
#[derive(Deserialize)]
struct Host {
statistics: Vec<Statistic>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
struct Statistic {
cpu_load: Vec<CpuLoad>,
}
#[derive(Deserialize)]
struct CpuLoad {
#[serde(rename = "cpu")]
identifier: String,
usr: f64,
nice: f64,
sys: f64,
iowait: f64,
irq: f64,
soft: f64,
steal: f64,
guest: f64,
gnice: f64,
idle: f64,
}
#[cfg(test)]
mod mp_stat {
use anyhow::Result;
use serde_json::json;
use super::MpStatOutput;
#[test]
fn deserializes() -> Result<(), serde_json::Error> {
let raw = json!({
"sysstat": {
"hosts": [{
"nodename": "kurante",
"sysname": "Linux",
"release": "5.17.7-zen1-1-zen",
"machine": "x86_64",
"number-of-cpus": 16,
"date": "05/27/2022",
"statistics": [{
"timestamp": "12:46:14 PM",
"cpu-load": [
{"cpu": "all", "usr": 3.19, "nice": 0.03, "sys": 0.73, "iowait": 0.08, "irq": 0.14, "soft": 0.08, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.77},
{"cpu": "0", "usr": 2.41, "nice": 0.00, "sys": 0.80, "iowait": 0.00, "irq": 0.80, "soft": 0.20, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.78},
{"cpu": "1", "usr": 1.20, "nice": 0.20, "sys": 0.80, "iowait": 1.00, "irq": 0.00, "soft": 0.20, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.61},
{"cpu": "2", "usr": 3.58, "nice": 0.00, "sys": 1.19, "iowait": 0.20, "irq": 0.60, "soft": 0.40, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 94.04},
{"cpu": "3", "usr": 3.21, "nice": 0.00, "sys": 1.00, "iowait": 0.00, "irq": 0.20, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.59},
{"cpu": "4", "usr": 3.62, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.98},
{"cpu": "5", "usr": 3.21, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.39},
{"cpu": "6", "usr": 4.22, "nice": 0.00, "sys": 0.60, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 95.18},
{"cpu": "7", "usr": 2.81, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.79},
{"cpu": "8", "usr": 2.01, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.20, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 97.38},
{"cpu": "9", "usr": 2.18, "nice": 0.00, "sys": 1.39, "iowait": 0.00, "irq": 0.20, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.23},
{"cpu": "10", "usr": 2.80, "nice": 0.00, "sys": 0.80, "iowait": 0.00, "irq": 0.00, "soft": 0.20, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.20},
{"cpu": "11", "usr": 5.18, "nice": 0.00, "sys": 1.00, "iowait": 0.00, "irq": 0.20, "soft": 0.20, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 93.43},
{"cpu": "12", "usr": 2.61, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 96.99},
{"cpu": "13", "usr": 4.83, "nice": 0.00, "sys": 0.40, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 94.77},
{"cpu": "14", "usr": 1.79, "nice": 0.20, "sys": 0.80, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 97.21},
{"cpu": "15", "usr": 5.41, "nice": 0.00, "sys": 0.80, "iowait": 0.00, "irq": 0.00, "soft": 0.00, "steal": 0.00, "guest": 0.00, "gnice": 0.00, "idle": 93.79}
]
}]
}]
}
});
serde_json::from_value::<MpStatOutput>(raw).map(drop)
}
}