time_util/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// One million for PPM calculations
6const MILLION: u64 = 1_000_000;
7
8/// A transformation from monotonic time to synthetic time, including an error bound on this
9/// synthetic time.
10#[derive(Clone, Default, Debug, Eq, PartialEq)]
11pub struct Transform<Reference, Output> {
12    /// An offset on the monotonic timeline in nanoseconds.
13    pub reference_offset: zx::Instant<Reference>,
14    /// An offset on the synthetic timeline in nanoseconds.
15    pub synthetic_offset: zx::Instant<Output>,
16    /// An adjustment to the standard 1 monotonic tick:1 synthetic tick rate in parts per million.
17    /// Positive values indicate the synthetic clock is moving faster than the monotonic clock.
18    pub rate_adjust_ppm: i32,
19    /// The error bound on synthetic clock at monotonic = monotonic_offset.
20    pub error_bound_at_offset: u64,
21    /// The growth in error bound per monotonic tick in parts per million.
22    pub error_bound_growth_ppm: u32,
23}
24
25impl<Reference: zx::Timeline + Copy, Output: zx::Timeline + Copy> Transform<Reference, Output> {
26    /// Returns the synthetic time at the supplied monotonic time.
27    pub fn synthetic(&self, reference: zx::Instant<Reference>) -> zx::Instant<Output> {
28        // Cast to i128 to avoid overflows in multiplication.
29        let reference_difference = (reference - self.reference_offset).into_nanos() as i128;
30        let synthetic_offset = self.synthetic_offset.into_nanos() as i128;
31        let synthetic_ticks = self.rate_adjust_ppm as i128 + MILLION as i128;
32        let reference_ticks = MILLION as i128;
33
34        let time_nanos =
35            (reference_difference * synthetic_ticks / reference_ticks) + synthetic_offset;
36        zx::Instant::from_nanos(time_nanos as i64)
37    }
38
39    /// Returns the error bound at the supplied monotonic time.
40    pub fn error_bound(&self, reference: zx::Instant<Reference>) -> u64 {
41        // Cast to i128 to avoid overflows in multiplication.
42        let reference_difference = (reference - self.reference_offset).into_nanos() as i128;
43        if reference_difference <= 0 {
44            // Assume the error bound was fixed at the supplied value before the reference time.
45            self.error_bound_at_offset
46        } else {
47            // Error bound increases linearly after the reference time.
48            let error_increase =
49                (reference_difference * self.error_bound_growth_ppm as i128) / MILLION as i128;
50            self.error_bound_at_offset + error_increase as u64
51        }
52    }
53
54    /// Returns the synthetic time on this `Transform` minus the synthetic time on `other`,
55    /// calculated at the supplied monotonic time.
56    pub fn difference(
57        &self,
58        other: &Self,
59        reference: zx::Instant<Reference>,
60    ) -> zx::Duration<Output> {
61        self.synthetic(reference) - other.synthetic(reference)
62    }
63
64    /// Returns a `ClockUpdate` that will set a `Clock` onto this `Transform` using data
65    /// from the supplied monotonic time.
66    pub fn jump_to(&self, reference: zx::Instant<Reference>) -> zx::ClockUpdate<Reference, Output> {
67        zx::ClockUpdate::<Reference, Output>::builder()
68            .absolute_value(reference, self.synthetic(reference))
69            .rate_adjust(self.rate_adjust_ppm)
70            .error_bounds(self.error_bound(reference))
71            .build()
72    }
73}
74
75impl<Reference: zx::Timeline, Output: zx::Timeline> From<&zx::Clock<Reference, Output>>
76    for Transform<Reference, Output>
77{
78    fn from(clock: &zx::Clock<Reference, Output>) -> Self {
79        // Clock read failures should only be caused by an invalid clock object.
80        let details = clock.get_details().expect("failed to get clock details");
81        // Cast to i64 to avoid overflows in multiplication.
82        let reference_ticks = details.reference_to_synthetic.rate.reference_ticks as i64;
83        let synthetic_ticks = details.reference_to_synthetic.rate.synthetic_ticks as i64;
84        let rate_adjust_ppm =
85            ((synthetic_ticks * MILLION as i64) / reference_ticks) - MILLION as i64;
86
87        Transform {
88            reference_offset: details.reference_to_synthetic.reference_offset,
89            synthetic_offset: details.reference_to_synthetic.synthetic_offset,
90            rate_adjust_ppm: rate_adjust_ppm as i32,
91            // Zircon clocks don't document the change in error over time. Assume a fixed error.
92            error_bound_at_offset: details.error_bounds,
93            error_bound_growth_ppm: 0,
94        }
95    }
96}
97
98/// Returns the time on the clock at a given monotonic reference time. This calculates the time
99/// based on the clock transform definition, which only contains the most recent segment. This
100/// is only useful for calculating the time for monotonic times close to the current time.
101pub fn time_at_monotonic<Reference: zx::Timeline, Output: zx::Timeline>(
102    clock: &zx::Clock<Reference, Output>,
103    reference: zx::Instant<Reference>,
104) -> zx::Instant<Output> {
105    let reference_nanos = reference.into_nanos() as i128;
106    // Clock read failures should only be caused by an invalid clock object.
107    let details = clock.get_details().expect("failed to get clock details");
108    // Calculate using the transform definition underlying a zircon clock.
109    // Cast to i128 to avoid overflows in multiplication.
110    let reference_offset = details.reference_to_synthetic.reference_offset.into_nanos() as i128;
111    let synthetic_offset = details.reference_to_synthetic.synthetic_offset.into_nanos() as i128;
112    let reference_ticks = details.reference_to_synthetic.rate.reference_ticks as i128;
113    let synthetic_ticks = details.reference_to_synthetic.rate.synthetic_ticks as i128;
114
115    let time_nanos = ((reference_nanos - reference_offset) * synthetic_ticks / reference_ticks)
116        + synthetic_offset;
117    zx::Instant::from_nanos(time_nanos as i64)
118}
119
120#[cfg(test)]
121mod test {
122    use super::*;
123    use test_util::{assert_geq, assert_leq};
124
125    const BACKSTOP: zx::SyntheticInstant = zx::SyntheticInstant::from_nanos(1234567890);
126    const TIME_DIFF: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(5);
127    const SLEW_RATE_PPM: i32 = 750;
128    const ONE_MILLION: i32 = 1_000_000;
129
130    const TEST_REFERENCE: zx::MonotonicInstant = zx::MonotonicInstant::from_nanos(70_000_000_000);
131    const TEST_OFFSET: zx::MonotonicDuration = zx::MonotonicDuration::from_nanos(5_000_000_000);
132    const TEST_ERROR_BOUND: u64 = 1234_000;
133    const TEST_ERROR_BOUND_GROWTH: u32 = 100;
134
135    const TOLERANCE: zx::MonotonicDuration = zx::MonotonicDuration::from_nanos(500_000_000);
136
137    #[fuchsia::test]
138    fn transform_properties_zero_rate_adjust() {
139        let transform = Transform {
140            reference_offset: TEST_REFERENCE,
141            synthetic_offset: zx::SyntheticInstant::from_nanos(
142                (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
143            ),
144            rate_adjust_ppm: 0,
145            error_bound_at_offset: TEST_ERROR_BOUND,
146            error_bound_growth_ppm: TEST_ERROR_BOUND_GROWTH,
147        };
148
149        assert_eq!(
150            transform.synthetic(TEST_REFERENCE).into_nanos(),
151            (TEST_REFERENCE + TEST_OFFSET).into_nanos()
152        );
153        assert_eq!(
154            transform
155                .synthetic(TEST_REFERENCE + zx::MonotonicDuration::from_millis(200))
156                .into_nanos(),
157            (TEST_REFERENCE + TEST_OFFSET + zx::MonotonicDuration::from_millis(200)).into_nanos(),
158        );
159        assert_eq!(
160            transform
161                .synthetic(TEST_REFERENCE - zx::MonotonicDuration::from_millis(100))
162                .into_nanos(),
163            (TEST_REFERENCE + TEST_OFFSET - zx::MonotonicDuration::from_millis(100)).into_nanos(),
164        );
165
166        assert_eq!(transform.error_bound(TEST_REFERENCE), TEST_ERROR_BOUND);
167        assert_eq!(
168            transform.error_bound(TEST_REFERENCE + zx::MonotonicDuration::from_millis(1)),
169            TEST_ERROR_BOUND + TEST_ERROR_BOUND_GROWTH as u64
170        );
171        assert_eq!(
172            transform.error_bound(TEST_REFERENCE - zx::MonotonicDuration::from_millis(1)),
173            TEST_ERROR_BOUND as u64
174        );
175    }
176
177    #[fuchsia::test]
178    fn transform_properties_positive_rate_adjust() {
179        let transform = Transform {
180            reference_offset: TEST_REFERENCE,
181            synthetic_offset: zx::SyntheticInstant::from_nanos(
182                (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
183            ),
184            rate_adjust_ppm: 25,
185            error_bound_at_offset: TEST_ERROR_BOUND,
186            error_bound_growth_ppm: 0,
187        };
188
189        assert_eq!(
190            transform.synthetic(TEST_REFERENCE).into_nanos(),
191            (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
192        );
193        assert_eq!(
194            transform
195                .synthetic(TEST_REFERENCE + zx::MonotonicDuration::from_millis(200))
196                .into_nanos(),
197            (TEST_REFERENCE
198                + TEST_OFFSET
199                + zx::MonotonicDuration::from_millis(200)
200                + zx::MonotonicDuration::from_nanos(25 * 200))
201            .into_nanos(),
202        );
203        assert_eq!(
204            transform
205                .synthetic(TEST_REFERENCE - zx::MonotonicDuration::from_millis(100))
206                .into_nanos(),
207            (TEST_REFERENCE + TEST_OFFSET
208                - zx::MonotonicDuration::from_millis(100)
209                - zx::MonotonicDuration::from_nanos(25 * 100))
210            .into_nanos(),
211        );
212
213        assert_eq!(transform.error_bound(TEST_REFERENCE), TEST_ERROR_BOUND);
214        assert_eq!(
215            transform.error_bound(TEST_REFERENCE + zx::MonotonicDuration::from_millis(1)),
216            TEST_ERROR_BOUND as u64
217        );
218        assert_eq!(
219            transform.error_bound(TEST_REFERENCE - zx::MonotonicDuration::from_millis(1)),
220            TEST_ERROR_BOUND as u64
221        );
222    }
223
224    #[fuchsia::test]
225    fn transform_properties_negative_rate_adjust() {
226        let transform = Transform {
227            reference_offset: TEST_REFERENCE,
228            synthetic_offset: zx::SyntheticInstant::from_nanos(
229                (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
230            ),
231            rate_adjust_ppm: -50,
232            error_bound_at_offset: TEST_ERROR_BOUND,
233            error_bound_growth_ppm: TEST_ERROR_BOUND_GROWTH,
234        };
235
236        assert_eq!(
237            transform.synthetic(TEST_REFERENCE).into_nanos(),
238            (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
239        );
240        assert_eq!(
241            transform
242                .synthetic(TEST_REFERENCE + zx::MonotonicDuration::from_millis(200))
243                .into_nanos(),
244            (TEST_REFERENCE + TEST_OFFSET + zx::MonotonicDuration::from_millis(200)
245                - zx::MonotonicDuration::from_nanos(50 * 200))
246            .into_nanos(),
247        );
248        assert_eq!(
249            transform
250                .synthetic(TEST_REFERENCE - zx::MonotonicDuration::from_millis(100))
251                .into_nanos(),
252            (TEST_REFERENCE + TEST_OFFSET - zx::MonotonicDuration::from_millis(100)
253                + zx::MonotonicDuration::from_nanos(50 * 100))
254            .into_nanos(),
255        );
256
257        assert_eq!(transform.error_bound(TEST_REFERENCE), TEST_ERROR_BOUND);
258        assert_eq!(
259            transform.error_bound(TEST_REFERENCE + zx::MonotonicDuration::from_seconds(1)),
260            TEST_ERROR_BOUND + (TEST_ERROR_BOUND_GROWTH * 1000) as u64
261        );
262        assert_eq!(
263            transform.error_bound(TEST_REFERENCE - zx::MonotonicDuration::from_seconds(1)),
264            TEST_ERROR_BOUND as u64
265        );
266    }
267
268    #[fuchsia::test]
269    fn transform_difference() {
270        let transform_1 = Transform {
271            reference_offset: TEST_REFERENCE,
272            synthetic_offset: zx::SyntheticInstant::from_nanos(
273                (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
274            ),
275            rate_adjust_ppm: 25,
276            error_bound_at_offset: TEST_ERROR_BOUND,
277            error_bound_growth_ppm: TEST_ERROR_BOUND_GROWTH,
278        };
279
280        let transform_2 = Transform {
281            reference_offset: TEST_REFERENCE,
282            synthetic_offset: zx::SyntheticInstant::from_nanos(TEST_REFERENCE.into_nanos()),
283            rate_adjust_ppm: -50,
284            error_bound_at_offset: TEST_ERROR_BOUND,
285            error_bound_growth_ppm: 0,
286        };
287
288        assert_eq!(
289            transform_1.difference(&transform_1, TEST_REFERENCE),
290            zx::SyntheticDuration::from_nanos(0)
291        );
292        assert_eq!(
293            transform_1.difference(&transform_2, TEST_REFERENCE),
294            zx::SyntheticDuration::from_nanos(TEST_OFFSET.into_nanos())
295        );
296        assert_eq!(
297            transform_2.difference(&transform_1, TEST_REFERENCE),
298            zx::SyntheticDuration::from_nanos(-TEST_OFFSET.into_nanos())
299        );
300        assert_eq!(
301            transform_1
302                .difference(&transform_2, TEST_REFERENCE + zx::MonotonicDuration::from_millis(500)),
303            zx::SyntheticDuration::from_nanos(TEST_OFFSET.into_nanos() + 75 * 500)
304        );
305        assert_eq!(
306            transform_1
307                .difference(&transform_2, TEST_REFERENCE - zx::MonotonicDuration::from_millis(300)),
308            zx::SyntheticDuration::from_nanos(TEST_OFFSET.into_nanos() - 75 * 300)
309        );
310    }
311
312    #[fuchsia::test]
313    fn transform_conversion() {
314        let transform = Transform {
315            reference_offset: TEST_REFERENCE,
316            synthetic_offset: zx::SyntheticInstant::from_nanos(
317                (TEST_REFERENCE + TEST_OFFSET).into_nanos(),
318            ),
319            rate_adjust_ppm: -15,
320            error_bound_at_offset: TEST_ERROR_BOUND,
321            error_bound_growth_ppm: 0,
322        };
323
324        let monotonic = zx::MonotonicInstant::get();
325        let clock_update = transform.jump_to(monotonic);
326        assert_eq!(
327            clock_update,
328            zx::ClockUpdate::builder()
329                .absolute_value(monotonic, transform.synthetic(monotonic))
330                .rate_adjust(-15)
331                .error_bounds(transform.error_bound(monotonic))
332                .build()
333        );
334
335        let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), None).unwrap();
336        clock.update(clock_update).unwrap();
337
338        let double_converted = Transform::from(&clock);
339        assert_eq!(double_converted.rate_adjust_ppm, transform.rate_adjust_ppm);
340        assert_eq!(double_converted.error_bound_at_offset, transform.error_bound_at_offset);
341        assert_eq!(double_converted.error_bound_growth_ppm, 0);
342        assert_eq!(double_converted.rate_adjust_ppm, transform.rate_adjust_ppm);
343        // Before RFC-0077 we accumulate some error in setting a clock, perform a coarse comparison.
344        let synthetic_from_double_converted = double_converted.synthetic(TEST_REFERENCE);
345        assert_geq!(
346            synthetic_from_double_converted.into_nanos(),
347            (TEST_REFERENCE + TEST_OFFSET - TOLERANCE).into_nanos()
348        );
349        assert_leq!(
350            synthetic_from_double_converted.into_nanos(),
351            (TEST_REFERENCE + TEST_OFFSET + TOLERANCE).into_nanos()
352        );
353    }
354
355    #[fuchsia::test]
356    fn time_at_monotonic_clock_not_started() {
357        let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), Some(BACKSTOP)).unwrap();
358        assert_eq!(time_at_monotonic(&clock, zx::MonotonicInstant::get() + TIME_DIFF), BACKSTOP);
359    }
360
361    #[fuchsia::test]
362    fn time_at_monotonic_clock_started() {
363        let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), Some(BACKSTOP)).unwrap();
364
365        let mono = zx::MonotonicInstant::get();
366        clock.update(zx::ClockUpdate::builder().absolute_value(mono, BACKSTOP)).unwrap();
367
368        let clock_time = time_at_monotonic(&clock, mono + TIME_DIFF);
369        assert_eq!(
370            clock_time,
371            BACKSTOP + zx::SyntheticDuration::from_nanos(TIME_DIFF.into_nanos())
372        );
373    }
374
375    #[fuchsia::test]
376    fn time_at_monotonic_clock_slew_fast() {
377        let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), Some(BACKSTOP)).unwrap();
378
379        let mono = zx::MonotonicInstant::get();
380        clock
381            .update(
382                zx::ClockUpdate::builder()
383                    .absolute_value(mono, BACKSTOP)
384                    .rate_adjust(SLEW_RATE_PPM),
385            )
386            .unwrap();
387
388        let clock_time = time_at_monotonic(&clock, mono + TIME_DIFF);
389        assert_eq!(
390            clock_time,
391            BACKSTOP
392                + zx::SyntheticDuration::from_nanos(
393                    (TIME_DIFF * (ONE_MILLION + SLEW_RATE_PPM) / ONE_MILLION).into_nanos()
394                )
395        );
396    }
397
398    #[fuchsia::test]
399    fn time_at_monotonic_clock_slew_slow() {
400        let clock = zx::SyntheticClock::create(zx::ClockOpts::empty(), Some(BACKSTOP)).unwrap();
401
402        let mono = zx::MonotonicInstant::get();
403        clock
404            .update(
405                zx::ClockUpdate::builder()
406                    .absolute_value(mono, BACKSTOP)
407                    .rate_adjust(-SLEW_RATE_PPM),
408            )
409            .unwrap();
410
411        let clock_time = time_at_monotonic(&clock, mono + TIME_DIFF);
412        assert_eq!(
413            clock_time,
414            BACKSTOP
415                + zx::SyntheticDuration::from_nanos(
416                    (TIME_DIFF * (ONE_MILLION - SLEW_RATE_PPM) / ONE_MILLION).into_nanos()
417                )
418        );
419    }
420}