use chrony_candm::reply; use figment::{ providers::{Format, Serialized, Toml}, util::map, value::Map, Figment, }; use gethostname::gethostname; use influxdb2::models::DataPoint; use serde_derive::{Deserialize, Serialize}; use std::time::{SystemTime, UNIX_EPOCH}; use std::{ net::{IpAddr, Ipv4Addr}, path::Path, }; #[derive(Serialize, Deserialize, Clone)] pub struct InfluxConfig { pub url: String, pub org: String, pub bucket: String, pub token: String, pub tags: Map, } impl Default for InfluxConfig { fn default() -> Self { let host = gethostname().into_string().unwrap(); InfluxConfig { url: "http://localhost:8086".into(), org: "default".into(), bucket: "default".into(), token: "".into(), tags: map! { "host".into() => host }, } } } #[derive(Serialize, Deserialize, Clone)] pub struct ChronyConfig { pub enabled: bool, pub timeout: u64, pub poll_interval: u64, pub measurement_prefix: String, pub tracking_measurement: String, pub sources_measurement: String, pub host: IpAddr, pub port: u16, } impl Default for ChronyConfig { fn default() -> Self { ChronyConfig { enabled: false, timeout: 5, poll_interval: 60, measurement_prefix: "chrony.".into(), tracking_measurement: "tracking".into(), sources_measurement: "sources".into(), host: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port: 323, } } } #[derive(Serialize, Deserialize, Clone, Default)] pub struct SourcesConfig { pub chrony: ChronyConfig, } #[derive(Serialize, Deserialize, Clone, Default)] pub struct Config { pub influxdb: InfluxConfig, pub sources: SourcesConfig, } pub fn load_config(filename: &Path) -> Result> { let config = Figment::from(Serialized::defaults(Config::default())) .merge(Toml::file(filename)) .extract()?; Ok(config) } pub fn datapoint_from_tracking( t: &reply::Tracking, config: &Config, ) -> Result> { let now = SystemTime::now().duration_since(UNIX_EPOCH)?; let measurement = config.sources.chrony.measurement_prefix.to_owned() + &config.sources.chrony.tracking_measurement; let mut builder = DataPoint::builder(&measurement).timestamp(now.as_nanos().try_into().unwrap()); for (key, value) in &config.influxdb.tags { builder = builder.tag(key, value); } let point = builder .field("ref_id", t.ref_id as i64) .field("ref_ip_addr", t.ip_addr.to_string()) .field("stratum", t.stratum as i64) .field("leap_status", t.leap_status as i64) .field("current_correction", f64::from(t.current_correction)) .field("last_offset", f64::from(t.last_offset)) .field("rms_offset", f64::from(t.rms_offset)) .field("freq_ppm", f64::from(t.freq_ppm)) .field("resid_freq_ppm", f64::from(t.resid_freq_ppm)) .field("skew_ppm", f64::from(t.skew_ppm)) .field("root_delay", f64::from(t.root_delay)) .field("root_dispersion", f64::from(t.root_dispersion)) .field("last_update_interval", f64::from(t.last_update_interval)) .build()?; Ok(point) } pub fn datapoint_from_sourcedata( d: &reply::SourceData, config: &Config, ) -> Result> { let now = SystemTime::now().duration_since(UNIX_EPOCH)?; let measurement = config.sources.chrony.measurement_prefix.to_owned() + &config.sources.chrony.sources_measurement; let mut builder = DataPoint::builder(&measurement).timestamp(now.as_nanos().try_into().unwrap()); for (key, value) in &config.influxdb.tags { builder = builder.tag(key, value) } builder = builder .tag("ip", d.ip_addr.to_string()) .field("poll", d.poll as i64) .field("stratum", d.stratum as i64) .field("state", d.state as i64) .field("mode", d.mode as i64) .field("flags", d.flags.bits() as i64) .field("reachability", d.reachability as i64) .field("since_sample", d.since_sample as i64) .field("orig_latest_meas", f64::from(d.orig_latest_meas)) .field("latest_meas", f64::from(d.latest_meas)) .field("latest_meas_err", f64::from(d.latest_meas_err)); let point = builder.build()?; Ok(point) }