Skip to main content

use_timeseries_store/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5use std::time::{Duration, SystemTime, UNIX_EPOCH};
6
7macro_rules! string_newtype {
8    ($(#[$meta:meta])* $name:ident) => {
9        $(#[$meta])*
10        #[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
11        pub struct $name(String);
12
13        impl $name {
14            /// Creates a new string-backed primitive.
15            pub fn new(value: impl Into<String>) -> Self {
16                Self(value.into())
17            }
18
19            /// Returns the stored string value.
20            pub fn as_str(&self) -> &str {
21                &self.0
22            }
23        }
24
25        impl AsRef<str> for $name {
26            fn as_ref(&self) -> &str {
27                self.as_str()
28            }
29        }
30
31        impl From<String> for $name {
32            fn from(value: String) -> Self {
33                Self::new(value)
34            }
35        }
36
37        impl From<&str> for $name {
38            fn from(value: &str) -> Self {
39                Self::new(value)
40            }
41        }
42
43        impl fmt::Display for $name {
44            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
45                formatter.write_str(self.as_str())
46            }
47        }
48    };
49}
50
51string_newtype! {
52    /// A time-series identifier.
53    SeriesId
54}
55string_newtype! {
56    /// A metric name.
57    MetricName
58}
59
60/// A timestamp backed by `std::time::SystemTime`.
61#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
62pub struct Timestamp(SystemTime);
63
64impl Timestamp {
65    /// Creates a timestamp.
66    pub const fn new(value: SystemTime) -> Self {
67        Self(value)
68    }
69
70    /// Creates a timestamp for the current system time.
71    pub fn now() -> Self {
72        Self(SystemTime::now())
73    }
74
75    /// Returns the stored system time.
76    pub const fn system_time(self) -> SystemTime {
77        self.0
78    }
79}
80
81impl fmt::Display for Timestamp {
82    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self.0.duration_since(UNIX_EPOCH) {
84            Ok(duration) => write!(formatter, "{}", duration.as_secs()),
85            Err(error) => write!(formatter, "-{}", error.duration().as_secs()),
86        }
87    }
88}
89
90/// A time-series numeric value.
91#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
92pub struct TimeSeriesValue(f64);
93
94impl TimeSeriesValue {
95    /// Creates a time-series value.
96    pub const fn new(value: f64) -> Self {
97        Self(value)
98    }
99
100    /// Returns the stored value.
101    pub const fn value(self) -> f64 {
102        self.0
103    }
104}
105
106impl fmt::Display for TimeSeriesValue {
107    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(formatter, "{}", self.0)
109    }
110}
111
112/// A single time-series point.
113#[derive(Clone, Debug, PartialEq)]
114pub struct TimeSeriesPoint {
115    series_id: SeriesId,
116    metric_name: MetricName,
117    timestamp: Timestamp,
118    value: TimeSeriesValue,
119}
120
121impl TimeSeriesPoint {
122    /// Creates a time-series point.
123    pub fn new(
124        series_id: SeriesId,
125        metric_name: MetricName,
126        timestamp: Timestamp,
127        value: TimeSeriesValue,
128    ) -> Self {
129        Self {
130            series_id,
131            metric_name,
132            timestamp,
133            value,
134        }
135    }
136
137    /// Returns the series identifier.
138    pub const fn series_id(&self) -> &SeriesId {
139        &self.series_id
140    }
141
142    /// Returns the metric name.
143    pub const fn metric_name(&self) -> &MetricName {
144        &self.metric_name
145    }
146
147    /// Returns the timestamp.
148    pub const fn timestamp(&self) -> Timestamp {
149        self.timestamp
150    }
151
152    /// Returns the value.
153    pub const fn value(&self) -> TimeSeriesValue {
154        self.value
155    }
156}
157
158/// A retention duration.
159#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
160pub struct RetentionWindow(Duration);
161
162impl RetentionWindow {
163    /// Creates a retention window.
164    pub const fn new(duration: Duration) -> Self {
165        Self(duration)
166    }
167
168    /// Returns the duration.
169    pub const fn duration(self) -> Duration {
170        self.0
171    }
172}
173
174/// A sampling interval duration.
175#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
176pub struct SamplingInterval(Duration);
177
178impl SamplingInterval {
179    /// Creates a sampling interval.
180    pub const fn new(duration: Duration) -> Self {
181        Self(duration)
182    }
183
184    /// Returns the duration.
185    pub const fn duration(self) -> Duration {
186        self.0
187    }
188}
189
190/// An aggregation window duration.
191#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
192pub struct AggregationWindow(Duration);
193
194impl AggregationWindow {
195    /// Creates an aggregation window.
196    pub const fn new(duration: Duration) -> Self {
197        Self(duration)
198    }
199
200    /// Returns the duration.
201    pub const fn duration(self) -> Duration {
202        self.0
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::{
209        AggregationWindow, MetricName, RetentionWindow, SamplingInterval, SeriesId,
210        TimeSeriesPoint, TimeSeriesValue, Timestamp,
211    };
212    use std::time::{Duration, UNIX_EPOCH};
213
214    #[test]
215    fn constructs_time_series_labels() {
216        assert_eq!(SeriesId::new("cpu:host-1").to_string(), "cpu:host-1");
217        assert_eq!(MetricName::new("cpu.usage").as_ref(), "cpu.usage");
218    }
219
220    #[test]
221    fn builds_points_and_windows() {
222        let point = TimeSeriesPoint::new(
223            SeriesId::new("host_1"),
224            MetricName::new("cpu.usage"),
225            Timestamp::new(UNIX_EPOCH + Duration::from_secs(10)),
226            TimeSeriesValue::new(0.75),
227        );
228        let retention = RetentionWindow::new(Duration::from_hours(24));
229        let sampling = SamplingInterval::new(Duration::from_mins(1));
230        let aggregation = AggregationWindow::new(Duration::from_mins(5));
231
232        assert_eq!(point.timestamp().to_string(), "10");
233        assert_eq!(point.value().to_string(), "0.75");
234        assert_eq!(retention.duration().as_secs(), 86_400);
235        assert_eq!(sampling.duration().as_secs(), 60);
236        assert_eq!(aggregation.duration().as_secs(), 300);
237    }
238}