utils/
time.rs

1// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::fmt;
5
6/// Constant to convert seconds to nanoseconds.
7pub const NANOS_PER_SECOND: u64 = 1_000_000_000;
8/// Constant to convert milliseconds to nanoseconds.
9pub const NANOS_PER_MILLISECOND: u64 = 1_000_000;
10
11/// Wrapper over `libc::clockid_t` to specify Linux Kernel clock source.
12#[derive(Debug)]
13pub enum ClockType {
14    /// Equivalent to `libc::CLOCK_MONOTONIC`.
15    Monotonic,
16    /// Equivalent to `libc::CLOCK_REALTIME`.
17    Real,
18    /// Equivalent to `libc::CLOCK_PROCESS_CPUTIME_ID`.
19    ProcessCpu,
20    /// Equivalent to `libc::CLOCK_THREAD_CPUTIME_ID`.
21    ThreadCpu,
22}
23
24impl From<ClockType> for libc::clockid_t {
25    fn from(clock_type: ClockType) -> Self {
26        match clock_type {
27            ClockType::Monotonic => libc::CLOCK_MONOTONIC,
28            ClockType::Real => libc::CLOCK_REALTIME,
29            ClockType::ProcessCpu => libc::CLOCK_PROCESS_CPUTIME_ID,
30            ClockType::ThreadCpu => libc::CLOCK_THREAD_CPUTIME_ID,
31        }
32    }
33}
34
35/// Structure representing the date in local time with nanosecond precision.
36#[derive(Debug)]
37pub struct LocalTime {
38    /// Seconds in current minute.
39    sec: i32,
40    /// Minutes in current hour.
41    min: i32,
42    /// Hours in current day, 24H format.
43    hour: i32,
44    /// Days in current month.
45    mday: i32,
46    /// Months in current year.
47    mon: i32,
48    /// Years passed since 1900 BC.
49    year: i32,
50    /// Nanoseconds in current second.
51    nsec: i64,
52}
53
54impl LocalTime {
55    /// Returns the [LocalTime](struct.LocalTime.html) structure for the calling moment.
56    pub fn now() -> LocalTime {
57        let mut timespec = libc::timespec {
58            tv_sec: 0,
59            tv_nsec: 0,
60        };
61        let mut tm: libc::tm = libc::tm {
62            tm_sec: 0,
63            tm_min: 0,
64            tm_hour: 0,
65            tm_mday: 0,
66            tm_mon: 0,
67            tm_year: 0,
68            tm_wday: 0,
69            tm_yday: 0,
70            tm_isdst: 0,
71            tm_gmtoff: 0,
72            tm_zone: std::ptr::null(),
73        };
74
75        // SAFETY: Safe because the parameters are valid.
76        unsafe {
77            libc::clock_gettime(libc::CLOCK_REALTIME, &mut timespec);
78            libc::localtime_r(&timespec.tv_sec, &mut tm);
79        }
80
81        LocalTime {
82            sec: tm.tm_sec,
83            min: tm.tm_min,
84            hour: tm.tm_hour,
85            mday: tm.tm_mday,
86            mon: tm.tm_mon,
87            year: tm.tm_year,
88            nsec: timespec.tv_nsec,
89        }
90    }
91}
92
93impl fmt::Display for LocalTime {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        write!(
96            f,
97            "{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
98            self.year + 1900,
99            self.mon + 1,
100            self.mday,
101            self.hour,
102            self.min,
103            self.sec,
104            self.nsec
105        )
106    }
107}
108
109/// Holds a micro-second resolution timestamp with both the real time and cpu time.
110#[derive(Debug, Clone)]
111pub struct TimestampUs {
112    /// Real time in microseconds.
113    pub time_us: u64,
114    /// Cpu time in microseconds.
115    pub cputime_us: u64,
116}
117
118impl Default for TimestampUs {
119    fn default() -> TimestampUs {
120        TimestampUs {
121            time_us: get_time_us(ClockType::Monotonic),
122            cputime_us: get_time_us(ClockType::ProcessCpu),
123        }
124    }
125}
126
127/// Returns a timestamp in nanoseconds from a monotonic clock.
128///
129/// Uses `_rdstc` on `x86_64` and [`get_time`](fn.get_time.html) on other architectures.
130pub fn timestamp_cycles() -> u64 {
131    #[cfg(target_arch = "x86_64")]
132    // SAFETY: Safe because there's nothing that can go wrong with this call.
133    unsafe {
134        std::arch::x86_64::_rdtsc()
135    }
136    #[cfg(not(target_arch = "x86_64"))]
137    {
138        get_time_ns(ClockType::Monotonic)
139    }
140}
141
142/// Returns a timestamp in nanoseconds based on the provided clock type.
143///
144/// # Arguments
145///
146/// * `clock_type` - Identifier of the Linux Kernel clock on which to act.
147pub fn get_time_ns(clock_type: ClockType) -> u64 {
148    let mut time_struct = libc::timespec {
149        tv_sec: 0,
150        tv_nsec: 0,
151    };
152    // SAFETY: Safe because the parameters are valid.
153    unsafe { libc::clock_gettime(clock_type.into(), &mut time_struct) };
154    u64::try_from(seconds_to_nanoseconds(time_struct.tv_sec).expect("Time conversion overflow"))
155        .unwrap()
156        + u64::try_from(time_struct.tv_nsec).unwrap()
157}
158
159/// Returns a timestamp in microseconds based on the provided clock type.
160///
161/// # Arguments
162///
163/// * `clock_type` - Identifier of the Linux Kernel clock on which to act.
164pub fn get_time_us(clock_type: ClockType) -> u64 {
165    get_time_ns(clock_type) / 1000
166}
167
168/// Returns a timestamp in milliseconds based on the provided clock type.
169///
170/// # Arguments
171///
172/// * `clock_type` - Identifier of the Linux Kernel clock on which to act.
173pub fn get_time_ms(clock_type: ClockType) -> u64 {
174    get_time_ns(clock_type) / NANOS_PER_MILLISECOND
175}
176
177/// Converts a timestamp in seconds to an equivalent one in nanoseconds.
178/// Returns `None` if the conversion overflows.
179///
180/// # Arguments
181///
182/// * `value` - Timestamp in seconds.
183pub fn seconds_to_nanoseconds(value: i64) -> Option<i64> {
184    value.checked_mul(i64::try_from(NANOS_PER_SECOND).unwrap())
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190
191    #[test]
192    fn test_get_time() {
193        for _ in 0..1000 {
194            assert!(get_time_ns(ClockType::Monotonic) <= get_time_ns(ClockType::Monotonic));
195        }
196
197        for _ in 0..1000 {
198            assert!(get_time_ns(ClockType::ProcessCpu) <= get_time_ns(ClockType::ProcessCpu));
199        }
200
201        for _ in 0..1000 {
202            assert!(get_time_ns(ClockType::ThreadCpu) <= get_time_ns(ClockType::ThreadCpu));
203        }
204
205        assert_ne!(get_time_ns(ClockType::Real), 0);
206        assert_ne!(get_time_us(ClockType::Real), 0);
207        assert!(get_time_ns(ClockType::Real) / 1000 <= get_time_us(ClockType::Real));
208        assert!(
209            get_time_ns(ClockType::Real) / NANOS_PER_MILLISECOND <= get_time_ms(ClockType::Real)
210        );
211    }
212
213    #[test]
214    fn test_local_time_display() {
215        let local_time = LocalTime {
216            sec: 30,
217            min: 15,
218            hour: 10,
219            mday: 4,
220            mon: 6,
221            year: 119,
222            nsec: 123_456_789,
223        };
224        assert_eq!(
225            String::from("2019-07-04T10:15:30.123456789"),
226            local_time.to_string()
227        );
228
229        let local_time = LocalTime {
230            sec: 5,
231            min: 5,
232            hour: 5,
233            mday: 23,
234            mon: 7,
235            year: 44,
236            nsec: 123,
237        };
238        assert_eq!(
239            String::from("1944-08-23T05:05:05.000000123"),
240            local_time.to_string()
241        );
242
243        let local_time = LocalTime::now();
244        assert!(local_time.mon >= 0 && local_time.mon <= 11);
245    }
246
247    #[test]
248    fn test_seconds_to_nanoseconds() {
249        assert_eq!(
250            u64::try_from(seconds_to_nanoseconds(100).unwrap()).unwrap(),
251            100 * NANOS_PER_SECOND
252        );
253
254        assert!(seconds_to_nanoseconds(9_223_372_037).is_none());
255    }
256}