Skip to main content

journal_common/
time.rs

1//! Time units for journal timestamps.
2//!
3//! Provides type-safe wrappers for seconds and microseconds to prevent unit confusion.
4
5use serde::{Deserialize, Serialize};
6use std::cell::Cell;
7use std::ops::{Add, Rem, Sub};
8#[cfg(all(not(unix), not(windows)))]
9use std::sync::OnceLock;
10#[cfg(all(not(unix), not(windows)))]
11use std::time::Instant;
12
13/// Timestamp in seconds since Unix epoch.
14///
15/// Used for histogram buckets, time ranges, and coarse-grained time operations.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
17#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
18pub struct Seconds(pub u32);
19
20/// Timestamp in microseconds since Unix epoch.
21///
22/// Used for journal entry timestamps and fine-grained time operations.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
24#[cfg_attr(feature = "allocative", derive(allocative::Allocative))]
25pub struct Microseconds(pub u64);
26
27impl Seconds {
28    /// Create a timestamp from seconds.
29    pub fn new(seconds: u32) -> Self {
30        Self(seconds)
31    }
32
33    /// Get the current time as seconds since Unix epoch.
34    pub fn now() -> Self {
35        Self(
36            std::time::SystemTime::now()
37                .duration_since(std::time::UNIX_EPOCH)
38                .expect("system time must be after UNIX_EPOCH")
39                .as_secs() as u32,
40        )
41    }
42
43    /// Get the raw seconds value.
44    pub fn get(self) -> u32 {
45        self.0
46    }
47
48    /// Convert to microseconds.
49    pub fn to_microseconds(self) -> Microseconds {
50        Microseconds(self.0 as u64 * 1_000_000)
51    }
52
53    /// Add two durations with saturation at the numeric bounds.
54    pub fn saturating_add(self, other: Self) -> Self {
55        Seconds(self.0.saturating_add(other.0))
56    }
57
58    /// Subtract two durations with saturation at the numeric bounds.
59    pub fn saturating_sub(self, other: Self) -> Self {
60        Seconds(self.0.saturating_sub(other.0))
61    }
62
63    /// Checked addition. Returns None if overflow occurred.
64    pub fn checked_add(self, other: Self) -> Option<Self> {
65        self.0.checked_add(other.0).map(Seconds)
66    }
67
68    /// Checked subtraction. Returns None if overflow occurred.
69    pub fn checked_sub(self, other: Self) -> Option<Self> {
70        self.0.checked_sub(other.0).map(Seconds)
71    }
72
73    /// Returns true if this duration is a multiple of the other duration.
74    ///
75    /// Useful for checking if bucket durations align.
76    pub fn is_multiple_of(self, other: Self) -> bool {
77        other.0 != 0 && self.0 % other.0 == 0
78    }
79}
80
81impl Microseconds {
82    /// Create a timestamp from microseconds.
83    pub fn new(microseconds: u64) -> Self {
84        Self(microseconds)
85    }
86
87    /// Get the current time as microseconds since Unix epoch.
88    pub fn now() -> Self {
89        Self(
90            std::time::SystemTime::now()
91                .duration_since(std::time::UNIX_EPOCH)
92                .expect("system time must be after UNIX_EPOCH")
93                .as_micros() as u64,
94        )
95    }
96
97    /// Get the raw microseconds value.
98    pub fn get(self) -> u64 {
99        self.0
100    }
101
102    /// Convert to seconds (truncates).
103    pub fn to_seconds(self) -> Seconds {
104        Seconds((self.0 / 1_000_000) as u32)
105    }
106
107    /// Add two durations with saturation at the numeric bounds.
108    pub fn saturating_add(self, other: Self) -> Self {
109        Microseconds(self.0.saturating_add(other.0))
110    }
111
112    /// Subtract two durations with saturation at the numeric bounds.
113    pub fn saturating_sub(self, other: Self) -> Self {
114        Microseconds(self.0.saturating_sub(other.0))
115    }
116
117    /// Checked addition. Returns None if overflow occurred.
118    pub fn checked_add(self, other: Self) -> Option<Self> {
119        self.0.checked_add(other.0).map(Microseconds)
120    }
121
122    /// Checked subtraction. Returns None if overflow occurred.
123    pub fn checked_sub(self, other: Self) -> Option<Self> {
124        self.0.checked_sub(other.0).map(Microseconds)
125    }
126
127    /// Returns true if this duration is a multiple of the other duration.
128    ///
129    /// Useful for checking if bucket durations align.
130    pub fn is_multiple_of(self, other: Self) -> bool {
131        other.0 != 0 && self.0 % other.0 == 0
132    }
133}
134
135impl From<Seconds> for Microseconds {
136    fn from(s: Seconds) -> Self {
137        s.to_microseconds()
138    }
139}
140
141impl From<u32> for Seconds {
142    fn from(s: u32) -> Self {
143        Seconds(s)
144    }
145}
146
147impl From<u64> for Microseconds {
148    fn from(us: u64) -> Self {
149        Microseconds(us)
150    }
151}
152
153// Arithmetic operators for Seconds
154impl Add for Seconds {
155    type Output = Self;
156
157    fn add(self, other: Self) -> Self {
158        Seconds(self.0 + other.0)
159    }
160}
161
162impl Sub for Seconds {
163    type Output = Self;
164
165    fn sub(self, other: Self) -> Self {
166        Seconds(self.0 - other.0)
167    }
168}
169
170impl Rem for Seconds {
171    type Output = Self;
172
173    fn rem(self, other: Self) -> Self {
174        Seconds(self.0 % other.0)
175    }
176}
177
178// Arithmetic operators for Microseconds
179impl Add for Microseconds {
180    type Output = Self;
181
182    fn add(self, other: Self) -> Self {
183        Microseconds(self.0 + other.0)
184    }
185}
186
187impl Sub for Microseconds {
188    type Output = Self;
189
190    fn sub(self, other: Self) -> Self {
191        Microseconds(self.0 - other.0)
192    }
193}
194
195impl Rem for Microseconds {
196    type Output = Self;
197
198    fn rem(self, other: Self) -> Self {
199        Microseconds(self.0 % other.0)
200    }
201}
202
203impl std::fmt::Display for Seconds {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        write!(f, "{}s", self.0)
206    }
207}
208
209impl std::fmt::Display for Microseconds {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        write!(f, "{}µs", self.0)
212    }
213}
214
215/// A monotonic realtime clock that ensures timestamps always move forward.
216///
217/// Wraps `SystemTime` but guarantees each `now()` call returns a timestamp
218/// strictly greater than all previous calls, even if the system clock jumps
219/// backwards. When the clock goes backwards, it increments from the last seen
220/// timestamp by one microsecond.
221#[derive(Debug)]
222pub struct RealtimeClock {
223    max_seen: Cell<u64>,
224}
225
226impl RealtimeClock {
227    /// Create a new realtime clock initialized with the current system time.
228    pub fn new() -> Self {
229        Self::with_initial(Microseconds::now())
230    }
231
232    /// Create a realtime clock initialized with a specific timestamp.
233    ///
234    /// Useful for resuming from a persisted state (e.g., last journal entry).
235    pub fn with_initial(initial: Microseconds) -> Self {
236        Self {
237            max_seen: Cell::new(initial.get()),
238        }
239    }
240
241    /// Get the current monotonic timestamp in microseconds since Unix epoch.
242    ///
243    /// Returns system time if it moved forward, otherwise returns last seen + 1µs.
244    pub fn now(&self) -> Microseconds {
245        let current = Microseconds::now();
246        let max = self.max_seen.get();
247
248        let next = if current.get() > max {
249            current.get()
250        } else {
251            max.saturating_add(1)
252        };
253
254        self.max_seen.set(next);
255        Microseconds::new(next)
256    }
257
258    /// Observe an external timestamp and return a monotonic realtime value.
259    ///
260    /// If the provided value is not strictly greater than the last seen timestamp,
261    /// returns `last_seen + 1us` to preserve strict monotonicity.
262    pub fn observe(&self, candidate: Microseconds) -> Microseconds {
263        let max = self.max_seen.get();
264        let next = if candidate.get() > max {
265            candidate.get()
266        } else {
267            max.saturating_add(1)
268        };
269
270        self.max_seen.set(next);
271        Microseconds::new(next)
272    }
273
274    /// Get the last seen timestamp without advancing the clock.
275    pub fn last_seen(&self) -> Microseconds {
276        Microseconds::new(self.max_seen.get())
277    }
278}
279
280impl Default for RealtimeClock {
281    fn default() -> Self {
282        Self::new()
283    }
284}
285
286/// Gets the current monotonic timestamp in microseconds since boot or system start.
287///
288/// Unix targets use `CLOCK_MONOTONIC`, which provides a monotonically increasing
289/// timestamp that is not affected by system clock adjustments but does not count
290/// time when the system is suspended. Windows uses unbiased interrupt time,
291/// which also counts only time spent in the working state since system start.
292///
293/// Other non-Unix targets fall back to process-local monotonic elapsed time.
294pub fn monotonic_now() -> std::io::Result<Microseconds> {
295    #[cfg(unix)]
296    {
297        let mut ts = std::mem::MaybeUninit::<libc::timespec>::uninit();
298        // SAFETY: `ts` points to valid uninitialized storage for
299        // `clock_gettime` to initialize on success.
300        // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage
301        let rc = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, ts.as_mut_ptr()) };
302        if rc != 0 {
303            return Err(std::io::Error::last_os_error());
304        }
305        // SAFETY: `assume_init` is reached only after `clock_gettime`
306        // returned success and initialized the full `timespec`.
307        // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage
308        let ts = unsafe { ts.assume_init() };
309        let seconds = u64::try_from(ts.tv_sec).map_err(|_| {
310            std::io::Error::new(
311                std::io::ErrorKind::InvalidData,
312                "CLOCK_MONOTONIC returned a negative seconds value",
313            )
314        })?;
315        let nanos = u64::try_from(ts.tv_nsec).map_err(|_| {
316            std::io::Error::new(
317                std::io::ErrorKind::InvalidData,
318                "CLOCK_MONOTONIC returned a negative nanoseconds value",
319            )
320        })?;
321        let micros = seconds
322            .checked_mul(1_000_000)
323            .and_then(|value| value.checked_add(nanos / 1_000))
324            .ok_or_else(|| {
325                std::io::Error::new(
326                    std::io::ErrorKind::InvalidData,
327                    "CLOCK_MONOTONIC microseconds overflowed u64",
328                )
329            })?;
330        Ok(Microseconds::new(micros))
331    }
332
333    #[cfg(windows)]
334    {
335        use windows_sys::Win32::System::WindowsProgramming::QueryUnbiasedInterruptTime;
336
337        let mut ticks_100ns = 0u64;
338        // SAFETY: Windows writes a 64-bit tick count through this valid stack
339        // pointer and does not retain the pointer after the call.
340        // nosemgrep: rust.lang.security.unsafe-usage.unsafe-usage
341        let ok = unsafe { QueryUnbiasedInterruptTime(&mut ticks_100ns) };
342        if ok == 0 {
343            return Err(std::io::Error::last_os_error());
344        }
345        Ok(Microseconds::new(ticks_100ns / 10))
346    }
347
348    #[cfg(all(not(unix), not(windows)))]
349    {
350        static START: OnceLock<Instant> = OnceLock::new();
351        let elapsed = START.get_or_init(Instant::now).elapsed();
352        let micros = u64::try_from(elapsed.as_micros()).map_err(|_| {
353            std::io::Error::new(
354                std::io::ErrorKind::InvalidData,
355                "monotonic elapsed microseconds overflowed u64",
356            )
357        })?;
358        Ok(Microseconds::new(micros))
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    #[test]
367    fn test_seconds_to_microseconds() {
368        let seconds = Seconds::new(42);
369        let micros = seconds.to_microseconds();
370        assert_eq!(micros.get(), 42_000_000);
371    }
372
373    #[test]
374    fn test_microseconds_to_seconds() {
375        let micros = Microseconds::new(42_500_000);
376        let seconds = micros.to_seconds();
377        assert_eq!(seconds.get(), 42);
378    }
379
380    #[test]
381    fn test_conversion_roundtrip() {
382        let original = Seconds::new(100);
383        let roundtrip = original.to_microseconds().to_seconds();
384        assert_eq!(original, roundtrip);
385    }
386
387    #[test]
388    fn test_from_conversions() {
389        let s: Seconds = 42u32.into();
390        assert_eq!(s.get(), 42);
391
392        let us: Microseconds = 42000u64.into();
393        assert_eq!(us.get(), 42000);
394    }
395
396    // Arithmetic operator tests for Seconds
397    #[test]
398    fn test_seconds_add() {
399        let a = Seconds::new(10);
400        let b = Seconds::new(20);
401        assert_eq!(a + b, Seconds::new(30));
402    }
403
404    #[test]
405    fn test_seconds_sub() {
406        let a = Seconds::new(30);
407        let b = Seconds::new(10);
408        assert_eq!(a - b, Seconds::new(20));
409    }
410
411    #[test]
412    #[should_panic]
413    fn test_seconds_sub_underflow() {
414        let a = Seconds::new(10);
415        let b = Seconds::new(20);
416        let _ = a - b; // Should panic
417    }
418
419    #[test]
420    fn test_seconds_rem() {
421        let a = Seconds::new(10);
422        let b = Seconds::new(3);
423        assert_eq!(a % b, Seconds::new(1));
424    }
425
426    #[test]
427    fn test_seconds_saturating_add() {
428        let a = Seconds::new(u32::MAX - 5);
429        let b = Seconds::new(10);
430        assert_eq!(a.saturating_add(b), Seconds::new(u32::MAX));
431    }
432
433    #[test]
434    fn test_seconds_saturating_sub() {
435        let a = Seconds::new(10);
436        let b = Seconds::new(20);
437        assert_eq!(a.saturating_sub(b), Seconds::new(0));
438    }
439
440    #[test]
441    fn test_seconds_checked_add() {
442        let a = Seconds::new(10);
443        let b = Seconds::new(20);
444        assert_eq!(a.checked_add(b), Some(Seconds::new(30)));
445
446        let c = Seconds::new(u32::MAX);
447        let d = Seconds::new(1);
448        assert_eq!(c.checked_add(d), None);
449    }
450
451    #[test]
452    fn test_seconds_checked_sub() {
453        let a = Seconds::new(30);
454        let b = Seconds::new(10);
455        assert_eq!(a.checked_sub(b), Some(Seconds::new(20)));
456
457        let c = Seconds::new(10);
458        let d = Seconds::new(20);
459        assert_eq!(c.checked_sub(d), None);
460    }
461
462    #[test]
463    fn test_seconds_is_multiple_of() {
464        let a = Seconds::new(60);
465        let b = Seconds::new(15);
466        assert!(a.is_multiple_of(b));
467
468        let c = Seconds::new(60);
469        let d = Seconds::new(17);
470        assert!(!c.is_multiple_of(d));
471
472        let e = Seconds::new(0);
473        let f = Seconds::new(10);
474        assert!(e.is_multiple_of(f));
475
476        let g = Seconds::new(10);
477        let h = Seconds::new(0);
478        assert!(!g.is_multiple_of(h)); // Division by zero case
479    }
480
481    // Arithmetic operator tests for Microseconds
482    #[test]
483    fn test_microseconds_add() {
484        let a = Microseconds::new(1000);
485        let b = Microseconds::new(2000);
486        assert_eq!(a + b, Microseconds::new(3000));
487    }
488
489    #[test]
490    fn test_microseconds_sub() {
491        let a = Microseconds::new(3000);
492        let b = Microseconds::new(1000);
493        assert_eq!(a - b, Microseconds::new(2000));
494    }
495
496    #[test]
497    #[should_panic]
498    fn test_microseconds_sub_underflow() {
499        let a = Microseconds::new(1000);
500        let b = Microseconds::new(2000);
501        let _ = a - b; // Should panic
502    }
503
504    #[test]
505    fn test_microseconds_rem() {
506        let a = Microseconds::new(1000);
507        let b = Microseconds::new(300);
508        assert_eq!(a % b, Microseconds::new(100));
509    }
510
511    #[test]
512    fn test_microseconds_saturating_add() {
513        let a = Microseconds::new(u64::MAX - 5);
514        let b = Microseconds::new(10);
515        assert_eq!(a.saturating_add(b), Microseconds::new(u64::MAX));
516    }
517
518    #[test]
519    fn test_microseconds_saturating_sub() {
520        let a = Microseconds::new(1000);
521        let b = Microseconds::new(2000);
522        assert_eq!(a.saturating_sub(b), Microseconds::new(0));
523    }
524
525    #[test]
526    fn test_microseconds_checked_add() {
527        let a = Microseconds::new(1000);
528        let b = Microseconds::new(2000);
529        assert_eq!(a.checked_add(b), Some(Microseconds::new(3000)));
530
531        let c = Microseconds::new(u64::MAX);
532        let d = Microseconds::new(1);
533        assert_eq!(c.checked_add(d), None);
534    }
535
536    #[test]
537    fn test_microseconds_checked_sub() {
538        let a = Microseconds::new(3000);
539        let b = Microseconds::new(1000);
540        assert_eq!(a.checked_sub(b), Some(Microseconds::new(2000)));
541
542        let c = Microseconds::new(1000);
543        let d = Microseconds::new(2000);
544        assert_eq!(c.checked_sub(d), None);
545    }
546
547    #[test]
548    fn test_microseconds_is_multiple_of() {
549        let a = Microseconds::new(60000);
550        let b = Microseconds::new(15000);
551        assert!(a.is_multiple_of(b));
552
553        let c = Microseconds::new(60000);
554        let d = Microseconds::new(17000);
555        assert!(!c.is_multiple_of(d));
556
557        let e = Microseconds::new(0);
558        let f = Microseconds::new(10000);
559        assert!(e.is_multiple_of(f));
560
561        let g = Microseconds::new(10000);
562        let h = Microseconds::new(0);
563        assert!(!g.is_multiple_of(h)); // Division by zero case
564    }
565
566    // RealtimeClock tests
567    #[test]
568    fn test_realtime_clock_monotonic() {
569        let clock = RealtimeClock::new();
570        let t1 = clock.now();
571        let t2 = clock.now();
572        let t3 = clock.now();
573
574        assert!(t2 > t1);
575        assert!(t3 > t2);
576    }
577
578    #[test]
579    fn test_realtime_clock_with_initial() {
580        let initial = Microseconds::new(1000000);
581        let clock = RealtimeClock::with_initial(initial);
582
583        assert_eq!(clock.last_seen(), initial);
584
585        let t1 = clock.now();
586        assert!(t1 >= initial);
587    }
588
589    #[test]
590    fn test_realtime_clock_handles_same_time() {
591        // Start with a specific timestamp
592        let initial = Microseconds::new(1000000);
593        let clock = RealtimeClock::with_initial(initial);
594
595        // Even if an observed timestamp doesn't advance, clock should increment.
596        let t1 = clock.observe(initial);
597        let t2 = clock.observe(initial);
598
599        assert!(t2 > t1);
600        assert_eq!(t2.get() - t1.get(), 1); // Should increment by 1 microsecond
601    }
602
603    #[test]
604    fn test_realtime_clock_last_seen() {
605        let clock = RealtimeClock::new();
606
607        let t1 = clock.now();
608        assert_eq!(clock.last_seen(), t1);
609
610        let t2 = clock.now();
611        assert_eq!(clock.last_seen(), t2);
612    }
613
614    #[test]
615    fn test_realtime_clock_forward_jump() {
616        // Start with a timestamp in the past
617        let past = Microseconds::new(1000000);
618        let clock = RealtimeClock::with_initial(past);
619
620        // When system time is ahead, it should use system time
621        let t1 = clock.now();
622        assert!(t1.get() > past.get());
623    }
624
625    #[test]
626    fn test_realtime_clock_observe_preserves_monotonicity() {
627        let clock = RealtimeClock::with_initial(Microseconds::new(1_000_000));
628
629        let t1 = clock.observe(Microseconds::new(900_000));
630        assert_eq!(t1.get(), 1_000_001);
631
632        let t2 = clock.observe(Microseconds::new(1_000_001));
633        assert_eq!(t2.get(), 1_000_002);
634
635        let t3 = clock.observe(Microseconds::new(1_500_000));
636        assert_eq!(t3.get(), 1_500_000);
637    }
638
639    #[test]
640    fn test_monotonic_now_is_ordered() {
641        let first = monotonic_now().expect("monotonic clock");
642        let second = monotonic_now().expect("monotonic clock");
643        assert!(second >= first);
644    }
645}