use crate::{ ChimemonSource, ChimemonSourceChannel, Config, SourceMetric, SourceReport, SourceReportDetails, SourceStatus, }; use async_trait::async_trait; use futures::{StreamExt, stream}; use influxdb2::models::DataPoint; use std::{ fs::File, io::Read, path::PathBuf, sync::Arc, time::{Duration, SystemTime, UNIX_EPOCH}, }; use tracing::{Instrument, debug, error, info, info_span, warn}; pub struct HwmonSource { config: Config, sensors: Vec>, } #[derive(Debug, Clone)] struct HwmonSensor { name: String, value_path: PathBuf, device: String, sensor: String, label: Option, tags: Arc>, } 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".to_owned(), name.to_owned()), ("device".to_owned(), device.to_owned()), ("sensor".to_owned(), sensor.to_owned()), ]; if let Some(label) = &label { tags_vec.push(("label".to_owned(), 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, f64)>, } impl SourceReportDetails for HwmonReport { fn is_healthy(&self) -> bool { //self.alarms.iter().any(|(_sensor, alarm)| *alarm) true } fn to_metrics(&self) -> Vec { let mut metrics = Vec::new(); for (sensor, value) in &self.values { metrics.push(SourceMetric::new_float( "hwmon_value", *value, sensor.tags.clone(), )) } // 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 { pub fn new(config: Config) -> Self { let sensors = config .sources .hwmon .sensors .iter() .map(|(k, v)| Arc::new(HwmonSensor::new(k, &v.name, &v.sensor))) .collect(); HwmonSource { config, sensors } } async fn get_raw_value(sensor: &HwmonSensor) -> Result { tokio::fs::read_to_string(&sensor.value_path).await } } #[async_trait] impl ChimemonSource for HwmonSource { async fn run(self, chan: ChimemonSourceChannel) { info!("hwmon task started"); let mut interval = tokio::time::interval(Duration::from_secs(self.config.sources.hwmon.interval)); loop { interval.tick().await; let mut values = Vec::new(); for s in &self.sensors { if let Ok(sensor_val) = HwmonSource::get_raw_value(s).await { debug!( "hwmon {} raw value {}", s.value_path.to_string_lossy(), sensor_val ); if let Ok(parsed) = sensor_val.trim().parse::() { values.push((s.clone(), parsed)); } else { error!( "Unable to parse sensor value {sensor_val} at {}", s.value_path.to_string_lossy() ); } } else { error!("Unable to get hwmon sensor value"); } } let report = SourceReport { name: "hwmon".to_owned(), 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})") } } } } }