170 lines
5.5 KiB
Rust
170 lines
5.5 KiB
Rust
use std::{fs::File, io::Read, path::PathBuf, sync::Arc};
|
|
|
|
use async_trait::async_trait;
|
|
use tokio::select;
|
|
use tokio_util::sync::CancellationToken;
|
|
use tracing::{debug, error, info, warn};
|
|
|
|
use crate::{
|
|
ChimemonSource, ChimemonSourceChannel, MetricTags, SourceMetric, SourceMetricSet, SourceReport,
|
|
SourceReportDetails, SourceStatus, config::HwmonConfig,
|
|
};
|
|
|
|
pub struct HwmonSource {
|
|
name: String,
|
|
config: HwmonConfig,
|
|
sensors: Vec<Arc<HwmonSensor>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct HwmonSensor {
|
|
name: String,
|
|
value_path: PathBuf,
|
|
device: String,
|
|
sensor: String,
|
|
label: Option<String>,
|
|
tags: Arc<MetricTags>,
|
|
}
|
|
|
|
impl HwmonSensor {
|
|
fn new(name: &str, device: &str, sensor: &str) -> Self {
|
|
let value_path = PathBuf::from(HWMON_ROOT)
|
|
.join(device)
|
|
.join(sensor.to_owned() + "_input");
|
|
let label_path_raw = PathBuf::from(HWMON_ROOT)
|
|
.join(device)
|
|
.join(sensor.to_owned() + "_label");
|
|
let label = if label_path_raw.is_file() {
|
|
let mut f =
|
|
File::open(&label_path_raw).expect(&format!("Unable to open `{label_path_raw:?}`"));
|
|
let mut label = String::new();
|
|
f.read_to_string(&mut label)
|
|
.expect(&format!("Unable to read from `{label_path_raw:?}"));
|
|
Some(label.trim().to_owned())
|
|
} else {
|
|
None
|
|
};
|
|
let mut tags_vec = vec![
|
|
("name", name.to_owned()),
|
|
("device", device.to_owned()),
|
|
("sensor", sensor.to_owned()),
|
|
];
|
|
if let Some(label) = &label {
|
|
tags_vec.push(("label", label.clone()))
|
|
}
|
|
Self {
|
|
value_path,
|
|
label,
|
|
device: device.to_owned(),
|
|
sensor: sensor.to_owned(),
|
|
name: name.to_owned(),
|
|
tags: Arc::new(tags_vec),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct HwmonReport {
|
|
values: Vec<(Arc<HwmonSensor>, f64)>,
|
|
}
|
|
|
|
impl SourceReportDetails for HwmonReport {
|
|
fn is_healthy(&self) -> bool {
|
|
//self.alarms.iter().any(|(_sensor, alarm)| *alarm)
|
|
true
|
|
}
|
|
fn to_metrics(&self) -> Vec<SourceMetricSet> {
|
|
let mut metrics = Vec::new();
|
|
for (sensor, value) in &self.values {
|
|
metrics.push(SourceMetricSet {
|
|
tags: sensor.tags.clone(),
|
|
metrics: vec![SourceMetric::new_float("value", *value)],
|
|
})
|
|
}
|
|
// for (sensor, alarm) in &self.alarms {
|
|
// metrics.push(SourceMetric::new_bool(
|
|
// "hwmon_alarm",
|
|
// *alarm,
|
|
// sensor.tags.clone(),
|
|
// ))
|
|
// }
|
|
|
|
metrics
|
|
}
|
|
}
|
|
|
|
const HWMON_ROOT: &str = "/sys/class/hwmon";
|
|
|
|
impl HwmonSource {
|
|
async fn get_raw_value(sensor: &HwmonSensor) -> Result<String, std::io::Error> {
|
|
tokio::fs::read_to_string(&sensor.value_path).await
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl ChimemonSource for HwmonSource {
|
|
type Config = HwmonConfig;
|
|
const TASK_NAME: &'static str = "hwmon-task";
|
|
fn new(name: &str, config: Self::Config) -> Self {
|
|
let sensors = config
|
|
.sensors
|
|
.iter()
|
|
.map(|(k, v)| Arc::new(HwmonSensor::new(k, &v.device, &v.sensor)))
|
|
.collect();
|
|
debug!("config: {config:?}");
|
|
HwmonSource {
|
|
name: name.to_owned(),
|
|
config,
|
|
sensors,
|
|
}
|
|
}
|
|
async fn run(self, chan: ChimemonSourceChannel, cancel: CancellationToken) {
|
|
info!("hwmon task started");
|
|
let mut interval = tokio::time::interval(self.config.interval);
|
|
loop {
|
|
select! {
|
|
_ = cancel.cancelled() => { return; },
|
|
_ = interval.tick() => {
|
|
let mut values = Vec::new();
|
|
for s in &self.sensors {
|
|
debug!("Sensor {s:?}");
|
|
match HwmonSource::get_raw_value(s).await {
|
|
Ok(sensor_val) => {
|
|
debug!(
|
|
"hwmon {} raw value {}",
|
|
s.value_path.to_string_lossy(),
|
|
sensor_val
|
|
);
|
|
if let Ok(parsed) = sensor_val.trim().parse::<f64>() {
|
|
values.push((s.clone(), parsed));
|
|
} else {
|
|
error!(
|
|
"Unable to parse sensor value {sensor_val} at {}",
|
|
s.value_path.to_string_lossy()
|
|
);
|
|
}
|
|
},
|
|
Err(e) => {
|
|
error!("Unable to get hwmon sensor value ({}) @ `{:?}`", e.to_string(), s.value_path.to_str());
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
let report = SourceReport {
|
|
name: self.name.clone(),
|
|
status: SourceStatus::Healthy,
|
|
details: Arc::new(HwmonReport { values }),
|
|
};
|
|
info!("Writing hwmon data");
|
|
match chan.send(report.into()) {
|
|
Ok(_) => {}
|
|
Err(e) => {
|
|
warn!("Unable to send to message channel ({e})")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|