Files
chimemon/src/lib.rs

364 lines
8.8 KiB
Rust

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<String, String>,
}
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<u64>")]
pub timeout: std::time::Duration,
#[serde_as(as = "DurationSeconds<u64>")]
pub tracking_interval: std::time::Duration,
#[serde_as(as = "DurationSeconds<u64>")]
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<u64>")]
pub interval: std::time::Duration,
pub measurement: String,
pub sensors: Map<String, HwmonSensorConfig>,
}
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<u64>")]
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<u64>")]
pub timeout: std::time::Duration,
#[serde_as(as = "DurationSeconds<u64>")]
pub status_interval: std::time::Duration,
#[serde_as(as = "DurationSeconds<u64>")]
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<Utc>,
pub offset: chrono::Duration,
pub leaps: isize,
pub leap_flag: bool,
pub valid: bool,
}
#[derive(Clone, Debug)]
pub enum SourceStatus {
Healthy,
LossOfSignal(Option<String>),
LossOfSync(Option<String>),
Other(Option<String>),
Unknown,
}
#[derive(Copy, Clone, Debug)]
pub enum MetricValue {
Int(i64),
Float(f64),
Bool(bool),
}
type MetricTag = (&'static str, String);
type MetricTags = Vec<MetricTag>;
#[derive(Clone, Debug)]
pub struct SourceMetric {
name: &'static str,
value: MetricValue,
tags: Arc<MetricTags>,
}
impl SourceMetric {
pub fn new_int(name: &'static str, value: i64, tags: Arc<MetricTags>) -> Self {
Self {
name: name,
value: MetricValue::Int(value),
tags,
}
}
pub fn new_float(name: &'static str, value: f64, tags: Arc<MetricTags>) -> Self {
Self {
name: name,
value: MetricValue::Float(value),
tags,
}
}
pub fn new_bool(name: &'static str, value: bool, tags: Arc<MetricTags>) -> Self {
Self {
name: name,
value: MetricValue::Bool(value),
tags,
}
}
}
pub trait SourceReportDetails: Debug + Send + Sync {
fn to_metrics(&self) -> Vec<SourceMetric>;
fn is_healthy(&self) -> bool;
}
#[derive(Clone, Debug)]
pub struct SourceReport {
pub name: String,
pub status: SourceStatus,
pub details: Arc<dyn SourceReportDetails>,
}
#[serde_as]
#[derive(Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct UCCMConfig {
pub enabled: bool,
pub port: String,
pub baud: u32,
#[serde_as(as = "DurationSeconds<u64>")]
pub status_interval: std::time::Duration,
#[serde_as(as = "DurationSeconds<u64>")]
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<NamedSourceConfig>,
pub targets: TargetsConfig,
}
impl Provider for Config {
fn metadata(&self) -> figment::Metadata {
figment::Metadata::named("Default config")
}
fn data(&self) -> Result<Map<figment::Profile, figment::value::Dict>, 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<DataPoint>),
TimeReport(TimeReport),
SourceReport(SourceReport),
}
impl From<DataPoint> for ChimemonMessage {
fn from(dp: DataPoint) -> Self {
ChimemonMessage::DataPoint(dp)
}
}
impl From<Vec<DataPoint>> for ChimemonMessage {
fn from(dps: Vec<DataPoint>) -> Self {
ChimemonMessage::DataPoints(dps)
}
}
impl From<TimeReport> for ChimemonMessage {
fn from(tr: TimeReport) -> Self {
ChimemonMessage::TimeReport(tr)
}
}
impl From<SourceReport> for ChimemonMessage {
fn from(sr: SourceReport) -> Self {
ChimemonMessage::SourceReport(sr)
}
}
pub type ChimemonSourceChannel = Sender<ChimemonMessage>;
pub type ChimemonTargetChannel = Receiver<ChimemonMessage>;
#[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);
}