pub mod sources; pub mod targets; use async_trait::async_trait; use chrono::{DateTime, Utc}; use figment::{ Figment, Provider, providers::{Format, Serialized, Toml}, util::map, value::Map, }; use gethostname::gethostname; use influxdb2::models::DataPoint; use serde_derive::{Deserialize, Serialize}; use serde_with::{DurationSeconds, serde_as}; use tokio::sync::broadcast::*; use std::{fmt::Debug, path::Path, sync::Arc}; #[derive(Serialize, Deserialize, Clone)] pub struct InfluxConfig { pub enabled: bool, 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 { enabled: false, url: "http://localhost:8086".into(), org: "default".into(), bucket: "default".into(), token: "".into(), tags: map! { "host".into() => host }, } } } #[serde_as] #[derive(Serialize, Deserialize, Clone)] #[serde(default)] pub struct ChronyConfig { pub enabled: bool, #[serde_as(as = "DurationSeconds")] pub timeout: std::time::Duration, #[serde_as(as = "DurationSeconds")] pub tracking_interval: std::time::Duration, #[serde_as(as = "DurationSeconds")] pub sources_interval: std::time::Duration, pub measurement_prefix: String, pub tracking_measurement: String, pub sources_measurement: String, pub host: String, } impl Default for ChronyConfig { fn default() -> Self { ChronyConfig { enabled: false, timeout: std::time::Duration::from_secs(5), tracking_interval: std::time::Duration::from_secs(60), sources_interval: std::time::Duration::from_secs(300), measurement_prefix: "chrony.".into(), tracking_measurement: "tracking".into(), sources_measurement: "sources".into(), host: "127.0.0.1:323".into(), } } } #[derive(Serialize, Deserialize, Clone)] #[serde(default)] pub struct ChronySockConfig { pub enabled: bool, pub sock: String, } impl Default for ChronySockConfig { fn default() -> Self { ChronySockConfig { enabled: false, sock: "".into(), } } } #[derive(Serialize, Deserialize, Clone)] pub struct HwmonSensorConfig { pub name: String, pub sensor: String, } #[serde_as] #[derive(Serialize, Deserialize, Clone)] #[serde(default)] pub struct HwmonConfig { pub enabled: bool, #[serde_as(as = "DurationSeconds")] pub interval: std::time::Duration, pub measurement: String, pub sensors: Map, } impl Default for HwmonConfig { fn default() -> Self { HwmonConfig { enabled: false, interval: std::time::Duration::from_secs(60), measurement: "hwmon".into(), sensors: map! {}, } } } #[serde_as] #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(default)] pub struct GpsdConfig { pub enabled: bool, #[serde_as(as = "DurationSeconds")] pub interval: std::time::Duration, pub host: String, } impl Default for GpsdConfig { fn default() -> Self { GpsdConfig { enabled: false, interval: std::time::Duration::from_secs(60), host: "localhost:2947".into(), } } } #[serde_as] #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(default)] pub struct Prs10Config { pub enabled: bool, pub port: String, pub baud: u32, #[serde_as(as = "DurationSeconds")] pub timeout: std::time::Duration, #[serde_as(as = "DurationSeconds")] pub status_interval: std::time::Duration, #[serde_as(as = "DurationSeconds")] pub stats_interval: std::time::Duration, } impl Default for Prs10Config { fn default() -> Self { Prs10Config { enabled: false, port: "/dev/ttyS0".into(), baud: 9600, timeout: std::time::Duration::from_secs(1), status_interval: std::time::Duration::from_secs(10), stats_interval: std::time::Duration::from_secs(30), } } } #[derive(Clone, Debug)] pub struct TimeReport { pub system_time: DateTime, pub offset: chrono::Duration, pub leaps: isize, pub leap_flag: bool, pub valid: bool, } #[derive(Clone, Debug)] pub enum SourceStatus { Healthy, LossOfSignal(Option), LossOfSync(Option), Other(Option), Unknown, } #[derive(Copy, Clone, Debug)] pub enum MetricValue { Int(i64), Float(f64), Bool(bool), } type MetricTag = (&'static str, String); type MetricTags = Vec; #[derive(Clone, Debug)] pub struct SourceMetric { name: &'static str, value: MetricValue, tags: Arc, } impl SourceMetric { pub fn new_int(name: &'static str, value: i64, tags: Arc) -> Self { Self { name: name, value: MetricValue::Int(value), tags, } } pub fn new_float(name: &'static str, value: f64, tags: Arc) -> Self { Self { name: name, value: MetricValue::Float(value), tags, } } pub fn new_bool(name: &'static str, value: bool, tags: Arc) -> Self { Self { name: name, value: MetricValue::Bool(value), tags, } } } pub trait SourceReportDetails: Debug + Send + Sync { fn to_metrics(&self) -> Vec; fn is_healthy(&self) -> bool; } #[derive(Clone, Debug)] pub struct SourceReport { pub name: String, pub status: SourceStatus, pub details: Arc, } #[serde_as] #[derive(Serialize, Deserialize, Clone)] #[serde(default)] pub struct UCCMConfig { pub enabled: bool, pub port: String, pub baud: u32, #[serde_as(as = "DurationSeconds")] pub status_interval: std::time::Duration, #[serde_as(as = "DurationSeconds")] pub timeout: std::time::Duration, pub measurement: String, } impl Default for UCCMConfig { fn default() -> Self { UCCMConfig { enabled: false, port: "/dev/ttyS0".into(), baud: 57600, status_interval: std::time::Duration::from_secs(10), timeout: std::time::Duration::from_secs(1), measurement: "uccm_gpsdo".into(), } } } #[derive(Serialize, Deserialize, Clone, Default)] pub struct SourcesConfig { pub chrony: ChronyConfig, pub hwmon: HwmonConfig, pub uccm: UCCMConfig, pub gpsd: GpsdConfig, pub prs10: Prs10Config, } #[derive(Serialize, Deserialize, Clone)] #[serde(tag = "type", rename_all = "snake_case")] pub enum SourceConfig { Chrony(ChronyConfig), Hwmon(HwmonConfig), Uccm(UCCMConfig), Gpsd(GpsdConfig), Prs10(Prs10Config), } #[derive(Serialize, Deserialize, Clone)] pub struct NamedSourceConfig { pub name: String, #[serde(flatten)] pub source: SourceConfig, } #[derive(Serialize, Deserialize, Clone, Default)] pub struct TargetsConfig { pub chrony: ChronySockConfig, } #[derive(Serialize, Deserialize, Clone, Default)] pub struct Config { pub influxdb: InfluxConfig, pub sources: Vec, pub targets: TargetsConfig, } impl Provider for Config { fn metadata(&self) -> figment::Metadata { figment::Metadata::named("Default config") } fn data(&self) -> Result, figment::Error> { Serialized::defaults(Config::default()).data() } } pub fn load_config(filename: &Path) -> Figment { Figment::from(Serialized::defaults(Config::default())).merge(Toml::file(filename)) } #[derive(Debug, Clone)] pub enum ChimemonMessage { DataPoint(DataPoint), DataPoints(Vec), TimeReport(TimeReport), SourceReport(SourceReport), } impl From for ChimemonMessage { fn from(dp: DataPoint) -> Self { ChimemonMessage::DataPoint(dp) } } impl From> for ChimemonMessage { fn from(dps: Vec) -> Self { ChimemonMessage::DataPoints(dps) } } impl From for ChimemonMessage { fn from(tr: TimeReport) -> Self { ChimemonMessage::TimeReport(tr) } } impl From for ChimemonMessage { fn from(sr: SourceReport) -> Self { ChimemonMessage::SourceReport(sr) } } pub type ChimemonSourceChannel = Sender; pub type ChimemonTargetChannel = Receiver; #[async_trait] pub trait ChimemonSource { type Config; fn new(name: &str, config: Self::Config) -> Self; async fn run(self, chan: ChimemonSourceChannel); } #[async_trait] pub trait ChimemonTarget { async fn run(self, chan: ChimemonTargetChannel); }