collectd_plugin/api/
cdtime.rs

1//! # `CdTime`
2//!
3//! Collectd stores time information in a custom type: `cdtime_t`. Below is a snippet from
4//! collectd's docs about why this custom format was chosen.
5//!
6//! The time is stored at a 2<sup>-30</sup> second resolution, i.e. the most significant 34 bit are used to
7//! store the time in seconds, the least significant bits store the sub-second part in something
8//! very close to nanoseconds. *The* big advantage of storing time in this manner is that comparing
9//! times and calculating differences is as simple as it is with `time_t`, i.e. a simple integer
10//! comparison / subtraction works.
11
12use crate::bindings::cdtime_t;
13use chrono::prelude::*;
14use chrono::Duration;
15
16/// `CdTime` allows for ergonomic interop between collectd's `cdtime_t` and chrono's `Duration` and
17/// `DateTime`. The single field represents epoch nanoseconds.
18#[derive(Debug, PartialEq, Eq, Clone, Copy)]
19pub struct CdTime(pub u64);
20
21impl<Tz: TimeZone> From<DateTime<Tz>> for CdTime {
22    fn from(dt: DateTime<Tz>) -> Self {
23        let sec_nanos = (dt.timestamp() as u64) * 1_000_000_000;
24        let nanos = u64::from(dt.timestamp_subsec_nanos());
25        CdTime(sec_nanos + nanos)
26    }
27}
28
29impl From<CdTime> for DateTime<Utc> {
30    fn from(v: CdTime) -> DateTime<Utc> {
31        let CdTime(ns) = v;
32        let secs = ns / 1_000_000_000;
33        let left = ns % 1_000_000_000;
34        Utc.timestamp_opt(secs as i64, left as u32)
35            .latest()
36            .unwrap_or_default()
37    }
38}
39
40impl From<Duration> for CdTime {
41    fn from(d: Duration) -> Self {
42        CdTime(d.num_nanoseconds().unwrap() as u64)
43    }
44}
45
46impl From<CdTime> for Duration {
47    fn from(v: CdTime) -> Self {
48        let CdTime(ns) = v;
49        Duration::nanoseconds(ns as i64)
50    }
51}
52
53impl From<cdtime_t> for CdTime {
54    fn from(d: cdtime_t) -> Self {
55        CdTime(collectd_to_nanos(d))
56    }
57}
58
59impl From<CdTime> for cdtime_t {
60    fn from(d: CdTime) -> Self {
61        let CdTime(x) = d;
62        nanos_to_collectd(x)
63    }
64}
65
66/// Convert epoch nanoseconds into collectd's 2<sup>-30</sup> second resolution
67pub fn nanos_to_collectd(nanos: u64) -> cdtime_t {
68    ((nanos / 1_000_000_000) << 30)
69        | ((((nanos % 1_000_000_000) << 30) + 500_000_000) / 1_000_000_000)
70}
71
72/// Convert collectd's 2^-30 second resolution into epoch nanoseconds
73fn collectd_to_nanos(cd: cdtime_t) -> u64 {
74    ((cd >> 30) * 1_000_000_000) + (((cd & 0x3fff_ffff) * 1_000_000_000 + (1 << 29)) >> 30)
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_nanos_to_collectd() {
83        // Taken from utils_time_test.c
84
85        assert_eq!(nanos_to_collectd(1439981652801860766), 1546168526406004689);
86        assert_eq!(nanos_to_collectd(1439981836985281914), 1546168724171447263);
87        assert_eq!(nanos_to_collectd(1439981880053705608), 1546168770415815077);
88    }
89
90    #[test]
91    fn test_collectd_to_nanos() {
92        assert_eq!(collectd_to_nanos(1546168526406004689), 1439981652801860766);
93        assert_eq!(collectd_to_nanos(1546168724171447263), 1439981836985281914);
94        assert_eq!(collectd_to_nanos(1546168770415815077), 1439981880053705608);
95    }
96
97    #[test]
98    fn test_collectd_to_duration() {
99        let v: cdtime_t = nanos_to_collectd(1_000_000_000);
100        let dur = Duration::from(CdTime::from(v));
101        assert_eq!(dur.num_seconds(), 1);
102    }
103
104    #[test]
105    fn test_collectd_to_datetime() {
106        let v: cdtime_t = nanos_to_collectd(1_000_000_000);
107        let dt: DateTime<Utc> = CdTime::from(v).into();
108        let res = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 1);
109        assert_eq!(res.unwrap(), dt);
110    }
111
112    #[test]
113    fn test_datetime_to_collectd() {
114        let res = Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 1);
115        let cd = CdTime::from(res.unwrap());
116        assert_eq!(cd.0, 1_000_000_000);
117    }
118}