windowed_stats/experimental/
clock.rs1use num::Integer;
13use std::fmt::Display;
14use std::mem;
15use thiserror::Error;
16
17#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)]
22#[error("non-monotonic tick or timestamp")]
23pub struct MonotonicityError;
24
25pub type Quanta = i64;
29
30pub trait QuantaExt {
31 fn into_nearest_unit_display(self) -> impl Display;
34}
35
36impl QuantaExt for Quanta {
37 fn into_nearest_unit_display(self) -> impl Display {
38 const SECONDS_PER_MINUTE: i64 = 60;
39 const SECONDS_PER_HOUR: i64 = SECONDS_PER_MINUTE * 60;
40 const SECONDS_PER_DAY: i64 = SECONDS_PER_HOUR * 24;
41
42 match (
43 self.div_rem(&SECONDS_PER_DAY),
44 self.div_rem(&SECONDS_PER_HOUR),
45 self.div_rem(&SECONDS_PER_MINUTE),
46 ) {
47 ((days, 0), _, _) => format!("{}d", days),
48 (_, (hours, 0), _) => format!("{}h", hours),
49 (_, _, (minutes, 0)) => format!("{}m", minutes),
50 _ => format!("{}s", self),
51 }
52 }
53}
54
55pub type Timestamp = fuchsia_async::BootInstant;
57
58pub trait TimestampExt {
59 fn quantize(self) -> Quanta;
61}
62
63impl TimestampExt for Timestamp {
64 fn quantize(self) -> Quanta {
65 (self - Timestamp::from_nanos(0)).into_quanta()
66 }
67}
68
69pub type Duration = zx::BootDuration;
71
72pub trait DurationExt {
73 const QUANTUM: Self;
78
79 fn from_quanta(quanta: Quanta) -> Self;
81
82 fn into_quanta(self) -> Quanta;
84}
85
86impl DurationExt for Duration {
87 const QUANTUM: Self = Duration::from_seconds(1);
88
89 fn from_quanta(quanta: Quanta) -> Self {
90 Duration::from_seconds(quanta)
91 }
92
93 fn into_quanta(self) -> Quanta {
94 self.into_seconds()
95 }
96}
97
98#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub struct Tick {
104 start: Timestamp,
105 end: Timestamp,
106 last_sample_timestamp: Option<Timestamp>,
107}
108
109impl Tick {
110 pub fn quantize(self) -> (Quanta, Quanta) {
112 let start = self.start.quantize();
113 let end = self.end.quantize();
114 (start, end)
115 }
116
117 pub fn start_has_sample(self, max_sampling_period: Quanta) -> bool {
120 let start = self.start.quantize();
121 match self.last_sample_timestamp {
122 Some(last_sample_timestamp) => {
123 let sample_time = last_sample_timestamp.quantize();
124 (start / max_sampling_period) == (sample_time / max_sampling_period)
125 }
126 None => false,
127 }
128 }
129}
130
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134pub struct ObservationTime {
135 pub(crate) last_update_timestamp: Timestamp,
136 pub(crate) last_sample_timestamp: Option<Timestamp>,
137}
138
139impl ObservationTime {
140 pub fn tick(
151 &mut self,
152 timestamp: Timestamp,
153 is_sample: bool,
154 ) -> Result<Tick, MonotonicityError> {
155 if timestamp < self.last_update_timestamp {
156 Err(MonotonicityError)
157 } else {
158 let new_observation_time = ObservationTime {
159 last_update_timestamp: timestamp,
160 last_sample_timestamp: if is_sample {
161 Some(timestamp)
162 } else {
163 self.last_sample_timestamp
164 },
165 };
166 let prev = mem::replace(self, new_observation_time);
167 Ok(Tick {
168 start: prev.last_update_timestamp,
169 end: timestamp,
170 last_sample_timestamp: prev.last_sample_timestamp,
171 })
172 }
173 }
174}
175
176impl Default for ObservationTime {
177 fn default() -> Self {
178 ObservationTime { last_update_timestamp: Timestamp::now(), last_sample_timestamp: None }
179 }
180}
181
182#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
186pub struct Timed<T> {
187 timestamp: Timestamp,
188 inner: T,
189}
190
191impl<T> Timed<T> {
192 pub fn now(inner: T) -> Self {
196 Timed { timestamp: Timestamp::now(), inner }
197 }
198
199 pub fn at(inner: T, timestamp: impl Into<Timestamp>) -> Self {
201 Timed { timestamp: timestamp.into(), inner }
202 }
203
204 pub fn map<U, F>(self, f: F) -> Timed<U>
206 where
207 F: FnOnce(T) -> U,
208 {
209 let Timed { timestamp, inner } = self;
210 Timed { timestamp, inner: f(inner) }
211 }
212
213 pub fn timestamp(&self) -> &Timestamp {
215 &self.timestamp
216 }
217
218 pub fn inner(&self) -> &T {
220 &self.inner
221 }
222}
223
224impl<T> Timed<Option<T>> {
225 pub fn transpose(self) -> Option<Timed<T>> {
226 let Timed { timestamp, inner } = self;
227 inner.map(|inner| Timed { timestamp, inner })
228 }
229}
230
231impl<T, E> Timed<Result<T, E>> {
232 pub fn transpose(self) -> Result<Timed<T>, E> {
233 let Timed { timestamp, inner } = self;
234 inner.map(|inner| Timed { timestamp, inner })
235 }
236}
237
238impl<T> From<Timed<T>> for (Timestamp, T) {
239 fn from(timed: Timed<T>) -> Self {
240 let Timed { timestamp, inner: sample } = timed;
241 (timestamp, sample)
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use crate::experimental::clock::{
248 Duration, DurationExt as _, MonotonicityError, ObservationTime, QuantaExt as _, Tick,
249 Timed, Timestamp, TimestampExt as _,
250 };
251 use fuchsia_async as fasync;
252
253 #[test]
254 fn quantize() {
255 let timestamp = Timestamp::from_nanos(0) + Duration::QUANTUM;
256 assert_eq!(timestamp.quantize(), 1);
257
258 let tick = Tick {
259 start: timestamp,
260 end: timestamp + Duration::from_quanta(3),
261 last_sample_timestamp: Some(Timestamp::from_nanos(-999)),
262 };
263 assert_eq!(tick.quantize(), (1, 4));
264 }
265
266 #[test]
267 fn start_has_sample() {
268 let tick = Tick {
269 start: Timestamp::from_nanos(9_000_000_000),
270 end: Timestamp::from_nanos(12_000_000_000),
271 last_sample_timestamp: Some(Timestamp::from_nanos(8_000_000_000)),
272 };
273 assert!(tick.start_has_sample(10));
274
275 let tick = Tick {
276 start: Timestamp::from_nanos(10_000_000_000),
277 end: Timestamp::from_nanos(13_000_000_000),
278 last_sample_timestamp: Some(Timestamp::from_nanos(8_000_000_000)),
279 };
280 assert!(!tick.start_has_sample(10));
281 }
282
283 #[test]
284 fn tick() {
285 const ZERO: Timestamp = Timestamp::from_nanos(0);
286 const MINUTE_ONE: Timestamp = Timestamp::from_nanos(60_000_000_000);
287 const MINUTE_THREE: Timestamp = Timestamp::from_nanos(180_000_000_000);
288 let mut last = ObservationTime { last_update_timestamp: ZERO, last_sample_timestamp: None };
289
290 let tick = last.tick(MINUTE_ONE, true).unwrap();
291 let expected_tick = Tick { start: ZERO, end: MINUTE_ONE, last_sample_timestamp: None };
292 let expected_last = ObservationTime {
293 last_update_timestamp: MINUTE_ONE,
294 last_sample_timestamp: Some(MINUTE_ONE),
295 };
296 assert_eq!(tick, expected_tick);
297 assert_eq!(last, expected_last);
298
299 let tick = last.tick(MINUTE_THREE, false).unwrap();
300 let expected_tick =
301 Tick { start: MINUTE_ONE, end: MINUTE_THREE, last_sample_timestamp: Some(MINUTE_ONE) };
302 let expected_last = ObservationTime {
303 last_update_timestamp: MINUTE_THREE,
304 last_sample_timestamp: Some(MINUTE_ONE),
305 };
306 assert_eq!(tick, expected_tick);
307 assert_eq!(last, expected_last);
308
309 let result = last.tick(MINUTE_ONE, false);
310 assert_eq!(result, Err(MonotonicityError));
311 }
312
313 #[test]
314 fn fmt_display_quanta() {
315 assert_eq!(0i64.into_nearest_unit_display().to_string(), "0d");
316 assert_eq!(1i64.into_nearest_unit_display().to_string(), "1s");
317 assert_eq!(5i64.into_nearest_unit_display().to_string(), "5s");
318 assert_eq!(60i64.into_nearest_unit_display().to_string(), "1m");
319 assert_eq!(65i64.into_nearest_unit_display().to_string(), "65s");
320 assert_eq!(120i64.into_nearest_unit_display().to_string(), "2m");
321 assert_eq!(3600i64.into_nearest_unit_display().to_string(), "1h");
322 assert_eq!(3605i64.into_nearest_unit_display().to_string(), "3605s");
323 assert_eq!(86400i64.into_nearest_unit_display().to_string(), "1d");
324 }
325
326 #[test]
327 fn timed_now_then_timestamp_eq_executor_now() {
328 const NOW: i64 = 3_000_000_000;
329
330 let executor = fasync::TestExecutor::new_with_fake_time();
331 executor.set_fake_time(fasync::MonotonicInstant::from_nanos(NOW));
332
333 let timed = Timed::now(());
334 let (timestamp, _) = timed.into();
335 assert_eq!(timestamp, Timestamp::from_nanos(NOW));
336 }
337
338 #[test]
339 fn timed_at_point_then_timestamp_eq_point() {
340 const AT: Timestamp = Timestamp::from_nanos(1);
341
342 let timed = Timed::at((), AT);
343 let (timestamp, _) = timed.into();
344 assert_eq!(timestamp, AT);
345 }
346
347 #[test]
348 fn map_timed_then_data_eq_output() {
349 let timed = Timed::at(9i64, Timestamp::from_nanos(1)).map(|n| n * 7);
350 let (_, n) = timed.into();
351 assert_eq!(n, 63);
352 }
353
354 #[test]
355 fn transpose_timed_then_output_is_congruent() {
356 const AT: Timestamp = Timestamp::from_nanos(1);
357
358 assert!(Timed::at(None::<()>, AT).transpose().is_none());
359 assert!(Timed::at(Some(()), AT).transpose().is_some());
360 }
361}