From d464cf8ee6ee04ec276b87c418e5636cf6eebe9d Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Tue, 3 Feb 2026 19:38:02 -0800 Subject: [PATCH] improve parsing of durations --- Cargo.lock | 157 ++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/lib.rs | 36 +++++++--- src/sources/chrony.rs | 10 +-- src/sources/gpsd.rs | 2 +- src/sources/hwmon.rs | 3 +- 6 files changed, 185 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09c0fc4..7429acc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -241,6 +247,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_repr", + "serde_with", "serialport", "tokio", "tokio-serial", @@ -386,6 +393,41 @@ dependencies = [ "memchr", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.114", +] + [[package]] name = "dashmap" version = "4.0.2" @@ -403,6 +445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -428,6 +471,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -672,13 +721,19 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.16.1" @@ -879,6 +934,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -900,6 +961,17 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -907,7 +979,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1516,6 +1590,26 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "regex" version = "1.12.2" @@ -1645,6 +1739,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1760,6 +1878,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "serialport" version = "4.8.1" @@ -2131,7 +2280,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap", + "indexmap 2.13.0", "toml_datetime", "winnow 0.5.40", ] @@ -2142,7 +2291,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde", "serde_spanned", "toml_datetime", diff --git a/Cargo.toml b/Cargo.toml index af6b4b7..73867d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ gethostname = "1.1.0" bitflags = "2.10.0" influxdb2 = "0.3.9" chrono = "0.4.43" +serde_with = "3.16.1" [dependencies.chrony-candm] git = "https://github.com/aws/chrony-candm" diff --git a/src/lib.rs b/src/lib.rs index 275a760..5c2f2ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ use figment::{ 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}; @@ -40,12 +41,16 @@ impl Default for InfluxConfig { } } +#[serde_as] #[derive(Serialize, Deserialize, Clone)] pub struct ChronyConfig { pub enabled: bool, - pub timeout: u64, - pub tracking_interval: u64, - pub sources_interval: u64, + #[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, @@ -56,9 +61,9 @@ impl Default for ChronyConfig { fn default() -> Self { ChronyConfig { enabled: false, - timeout: 5, - tracking_interval: 60, - sources_interval: 300, + 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(), @@ -88,10 +93,12 @@ pub struct HwmonSensorConfig { pub sensor: String, } +#[serde_as] #[derive(Serialize, Deserialize, Clone)] pub struct HwmonConfig { pub enabled: bool, - pub interval: u64, + #[serde_as(as = "DurationSeconds")] + pub interval: std::time::Duration, pub measurement: String, pub sensors: Map, } @@ -100,17 +107,19 @@ impl Default for HwmonConfig { fn default() -> Self { HwmonConfig { enabled: false, - interval: 60, + interval: std::time::Duration::from_secs(60), measurement: "hwmon".into(), sensors: map! {}, } } } +#[serde_as] #[derive(Serialize, Deserialize, Clone, Debug)] pub struct GpsdConfig { pub enabled: bool, - pub interval: u64, + #[serde_as(as = "DurationSeconds")] + pub interval: std::time::Duration, pub host: String, } @@ -118,19 +127,23 @@ impl Default for GpsdConfig { fn default() -> Self { GpsdConfig { enabled: false, - interval: 60, + interval: std::time::Duration::from_secs(60), host: "localhost:2947".into(), } } } +#[serde_as] #[derive(Serialize, Deserialize, Clone, Debug)] 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, } @@ -217,12 +230,15 @@ pub struct SourceReport { pub details: Arc, } +#[serde_as] #[derive(Serialize, Deserialize, Clone)] 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, } diff --git a/src/sources/chrony.rs b/src/sources/chrony.rs index a38f5e8..80e08f9 100644 --- a/src/sources/chrony.rs +++ b/src/sources/chrony.rs @@ -162,7 +162,7 @@ impl ChronyClient { .expect("Unable to parse host:port:"); let client_options = ClientOptions { n_tries: 3, - timeout: Duration::from_secs(config.sources.chrony.timeout), + timeout: config.sources.chrony.timeout, }; ChronyClient { server, @@ -298,12 +298,8 @@ impl ChimemonSource for ChronyClient { async fn run(self, chan: ChimemonSourceChannel) { info!("Chrony task started"); - let mut t_interval = tokio::time::interval(Duration::from_secs( - self.config.sources.chrony.tracking_interval, - )); - let mut s_interval = tokio::time::interval(Duration::from_secs( - self.config.sources.chrony.sources_interval, - )); + let mut t_interval = tokio::time::interval(self.config.sources.chrony.tracking_interval); + let mut s_interval = tokio::time::interval(self.config.sources.chrony.sources_interval); let t_future = async { let lchan = chan.clone(); diff --git a/src/sources/gpsd.rs b/src/sources/gpsd.rs index 5cd4891..4fc2600 100644 --- a/src/sources/gpsd.rs +++ b/src/sources/gpsd.rs @@ -166,7 +166,7 @@ impl ChimemonSource for GpsdSource { async fn run(mut self, mut chan: ChimemonSourceChannel) { info!("gpsd task started"); self.conn.conn().await.unwrap(); - let mut ticker = interval(Duration::from_secs(self.config.sources.gpsd.interval)); + let mut ticker = interval(self.config.sources.gpsd.interval); let mut params = WatchParams::default(); params.json = Some(true); diff --git a/src/sources/hwmon.rs b/src/sources/hwmon.rs index ac14cc4..2dfccd6 100644 --- a/src/sources/hwmon.rs +++ b/src/sources/hwmon.rs @@ -113,8 +113,7 @@ impl HwmonSource { 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)); + let mut interval = tokio::time::interval(self.config.sources.hwmon.interval); loop { interval.tick().await; let mut values = Vec::new();