364 lines
8.8 KiB
Rust
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);
|
|
}
|