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