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 fn from_quanta(quanta: Quanta) -> Self;
64}
65
66impl TimestampExt for Timestamp {
67 fn quantize(self) -> Quanta {
68 (self - Timestamp::from_nanos(0)).into_quanta()
69 }
70
71 fn from_quanta(quanta: Quanta) -> Self {
72 Timestamp::from_nanos(0) + Duration::from_quanta(quanta)
73 }
74}
75
76pub type Duration = zx::BootDuration;
78
79pub trait DurationExt {
80 const QUANTUM: Self;
85
86 fn from_quanta(quanta: Quanta) -> Self;
88
89 fn into_quanta(self) -> Quanta;
91}
92
93impl DurationExt for Duration {
94 const QUANTUM: Self = Duration::from_seconds(1);
95
96 fn from_quanta(quanta: Quanta) -> Self {
97 Duration::from_seconds(quanta)
98 }
99
100 fn into_quanta(self) -> Quanta {
101 self.into_seconds()
102 }
103}
104
105#[derive(Clone, Copy, Debug, Eq, PartialEq)]
110pub struct Tick {
111 start: Timestamp,
112 end: Timestamp,
113 last_sample_timestamp: Option<Timestamp>,
114}
115
116impl Tick {
117 pub fn quantize(self) -> (Quanta, Quanta) {
119 let start = self.start.quantize();
120 let end = self.end.quantize();
121 (start, end)
122 }
123
124 pub fn start_has_sample(self, max_sampling_period: Quanta) -> bool {
127 let start = self.start.quantize();
128 match self.last_sample_timestamp {
129 Some(last_sample_timestamp) => {
130 let sample_time = last_sample_timestamp.quantize();
131 (start / max_sampling_period) == (sample_time / max_sampling_period)
132 }
133 None => false,
134 }
135 }
136}
137
138#[derive(Clone, Copy, Debug, Eq, PartialEq)]
141pub struct ObservationTime {
142 pub(crate) last_update_timestamp: Timestamp,
143 pub(crate) last_sample_timestamp: Option<Timestamp>,
144}
145
146impl ObservationTime {
147 pub fn tick(
158 &mut self,
159 timestamp: Timestamp,
160 is_sample: bool,
161 ) -> Result<Tick, MonotonicityError> {
162 if timestamp < self.last_update_timestamp {
163 Err(MonotonicityError)
164 } else {
165 let new_observation_time = ObservationTime {
166 last_update_timestamp: timestamp,
167 last_sample_timestamp: if is_sample {
168 Some(timestamp)
169 } else {
170 self.last_sample_timestamp
171 },
172 };
173 let prev = mem::replace(self, new_observation_time);
174 Ok(Tick {
175 start: prev.last_update_timestamp,
176 end: timestamp,
177 last_sample_timestamp: prev.last_sample_timestamp,
178 })
179 }
180 }
181
182 pub(crate) fn at(last_update_timestamp: Timestamp) -> Self {
183 Self { last_update_timestamp, last_sample_timestamp: None }
184 }
185}
186
187impl Default for ObservationTime {
188 fn default() -> Self {
189 ObservationTime { last_update_timestamp: Timestamp::now(), last_sample_timestamp: None }
190 }
191}
192
193#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
197pub struct Timed<T> {
198 timestamp: Timestamp,
199 inner: T,
200}
201
202impl<T> Timed<T> {
203 pub fn now(inner: T) -> Self {
207 Timed { timestamp: Timestamp::now(), inner }
208 }
209
210 pub fn at(inner: T, timestamp: impl Into<Timestamp>) -> Self {
212 Timed { timestamp: timestamp.into(), inner }
213 }
214
215 pub fn map<U, F>(self, f: F) -> Timed<U>
217 where
218 F: FnOnce(T) -> U,
219 {
220 let Timed { timestamp, inner } = self;
221 Timed { timestamp, inner: f(inner) }
222 }
223
224 pub fn timestamp(&self) -> &Timestamp {
226 &self.timestamp
227 }
228
229 pub fn inner(&self) -> &T {
231 &self.inner
232 }
233}
234
235impl<T> Timed<Option<T>> {
236 pub fn transpose(self) -> Option<Timed<T>> {
237 let Timed { timestamp, inner } = self;
238 inner.map(|inner| Timed { timestamp, inner })
239 }
240}
241
242impl<T, E> Timed<Result<T, E>> {
243 pub fn transpose(self) -> Result<Timed<T>, E> {
244 let Timed { timestamp, inner } = self;
245 inner.map(|inner| Timed { timestamp, inner })
246 }
247}
248
249impl<T> From<Timed<T>> for (Timestamp, T) {
250 fn from(timed: Timed<T>) -> Self {
251 let Timed { timestamp, inner: sample } = timed;
252 (timestamp, sample)
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use crate::experimental::clock::{
259 Duration, DurationExt as _, MonotonicityError, ObservationTime, QuantaExt as _, Tick,
260 Timed, Timestamp, TimestampExt as _,
261 };
262 use fuchsia_async as fasync;
263
264 #[test]
265 fn quantize() {
266 let timestamp = Timestamp::from_nanos(0) + Duration::QUANTUM;
267 assert_eq!(timestamp.quantize(), 1);
268
269 let tick = Tick {
270 start: timestamp,
271 end: timestamp + Duration::from_quanta(3),
272 last_sample_timestamp: Some(Timestamp::from_nanos(-999)),
273 };
274 assert_eq!(tick.quantize(), (1, 4));
275 }
276
277 #[test]
278 fn start_has_sample() {
279 let tick = Tick {
280 start: Timestamp::from_nanos(9_000_000_000),
281 end: Timestamp::from_nanos(12_000_000_000),
282 last_sample_timestamp: Some(Timestamp::from_nanos(8_000_000_000)),
283 };
284 assert!(tick.start_has_sample(10));
285
286 let tick = Tick {
287 start: Timestamp::from_nanos(10_000_000_000),
288 end: Timestamp::from_nanos(13_000_000_000),
289 last_sample_timestamp: Some(Timestamp::from_nanos(8_000_000_000)),
290 };
291 assert!(!tick.start_has_sample(10));
292 }
293
294 #[test]
295 fn tick() {
296 const ZERO: Timestamp = Timestamp::from_nanos(0);
297 const MINUTE_ONE: Timestamp = Timestamp::from_nanos(60_000_000_000);
298 const MINUTE_THREE: Timestamp = Timestamp::from_nanos(180_000_000_000);
299 let mut last = ObservationTime { last_update_timestamp: ZERO, last_sample_timestamp: None };
300
301 let tick = last.tick(MINUTE_ONE, true).unwrap();
302 let expected_tick = Tick { start: ZERO, end: MINUTE_ONE, last_sample_timestamp: None };
303 let expected_last = ObservationTime {
304 last_update_timestamp: MINUTE_ONE,
305 last_sample_timestamp: Some(MINUTE_ONE),
306 };
307 assert_eq!(tick, expected_tick);
308 assert_eq!(last, expected_last);
309
310 let tick = last.tick(MINUTE_THREE, false).unwrap();
311 let expected_tick =
312 Tick { start: MINUTE_ONE, end: MINUTE_THREE, last_sample_timestamp: Some(MINUTE_ONE) };
313 let expected_last = ObservationTime {
314 last_update_timestamp: MINUTE_THREE,
315 last_sample_timestamp: Some(MINUTE_ONE),
316 };
317 assert_eq!(tick, expected_tick);
318 assert_eq!(last, expected_last);
319
320 let result = last.tick(MINUTE_ONE, false);
321 assert_eq!(result, Err(MonotonicityError));
322 }
323
324 #[test]
325 fn fmt_display_quanta() {
326 assert_eq!(0i64.into_nearest_unit_display().to_string(), "0d");
327 assert_eq!(1i64.into_nearest_unit_display().to_string(), "1s");
328 assert_eq!(5i64.into_nearest_unit_display().to_string(), "5s");
329 assert_eq!(60i64.into_nearest_unit_display().to_string(), "1m");
330 assert_eq!(65i64.into_nearest_unit_display().to_string(), "65s");
331 assert_eq!(120i64.into_nearest_unit_display().to_string(), "2m");
332 assert_eq!(3600i64.into_nearest_unit_display().to_string(), "1h");
333 assert_eq!(3605i64.into_nearest_unit_display().to_string(), "3605s");
334 assert_eq!(86400i64.into_nearest_unit_display().to_string(), "1d");
335 }
336
337 #[test]
338 fn timed_now_then_timestamp_eq_executor_now() {
339 const NOW: i64 = 3_000_000_000;
340
341 let executor = fasync::TestExecutor::new_with_fake_time();
342 executor.set_fake_time(fasync::MonotonicInstant::from_nanos(NOW));
343
344 let timed = Timed::now(());
345 let (timestamp, _) = timed.into();
346 assert_eq!(timestamp, Timestamp::from_nanos(NOW));
347 }
348
349 #[test]
350 fn timed_at_point_then_timestamp_eq_point() {
351 const AT: Timestamp = Timestamp::from_nanos(1);
352
353 let timed = Timed::at((), AT);
354 let (timestamp, _) = timed.into();
355 assert_eq!(timestamp, AT);
356 }
357
358 #[test]
359 fn map_timed_then_data_eq_output() {
360 let timed = Timed::at(9i64, Timestamp::from_nanos(1)).map(|n| n * 7);
361 let (_, n) = timed.into();
362 assert_eq!(n, 63);
363 }
364
365 #[test]
366 fn transpose_timed_then_output_is_congruent() {
367 const AT: Timestamp = Timestamp::from_nanos(1);
368
369 assert!(Timed::at(None::<()>, AT).transpose().is_none());
370 assert!(Timed::at(Some(()), AT).transpose().is_some());
371 }
372}