rustics/time.rs
1//
2// Copyright 2024 Jonathan L Bertoni
3//
4// This code is available under the Berkeley 2-Clause, Berkeley 3-clause,
5// and MIT licenses.
6//
7
8//!
9//! ## Types
10//!
11//! * Timer
12//! * The Timer trait defines the interface for time operations needed by the
13//! statistics functions.
14//!
15//! * DurationTimer
16//! * DurationTimer provides a Timer interface to the standard rust Duration type,
17//! which measures wall-clock time.
18//!
19//! * SimpleClock
20//! * The SimpleClock trait is an abstraction that can be used to implement
21//! platform-specific Timer instances. Something like a cycle counter would
22//! be an example of a timer that can be used as a SimpleClock.
23//!
24//! * ClockTimer
25//! * Clock timer is an implementation of Timer that uses a
26//! SimpleClock underneath. It is a standard bridge from
27//! implementation-specific timers to the Rustics types.
28//!
29//! ## Example
30//!```
31//! use rustics::time::SimpleClock;
32//!
33//! // This is an example implementation of the SimpleClock trait. It
34//! // simply returns a series of time values increasing by one tick
35//! // per invocation.
36//!
37//! struct ExampleClock {
38//! current_time: u128,
39//! hz: u128,
40//! }
41//!
42//! impl ExampleClock {
43//! fn new(start_time: u128, hz: u128) -> ExampleClock {
44//! let current_time = start_time;
45//!
46//! ExampleClock { current_time, hz }
47//! }
48//! }
49//!
50//! impl SimpleClock for ExampleClock {
51//! fn get_time(&mut self) -> u128 {
52//! self.current_time += 1;
53//! self.current_time
54//! }
55//!
56//! fn hz(&self) -> u128 {
57//! self.hz
58//! }
59//! }
60//!
61//! let start_time = 1;
62//! let hz = 1_000_000_000;
63//! let mut clock = ExampleClock::new(start_time, hz);
64//!
65//! // Get a few time values.
66//!
67//! for i in 1..100 {
68//! let time = clock.get_time();
69//!
70//! assert!(time == start_time + i);
71//! }
72//!
73//! assert!(clock.hz() == hz);
74//!```
75
76use std::time::Instant;
77use std::rc::Rc;
78use std::cell::RefCell;
79
80use crate::TimerBox;
81use crate::timer_box;
82
83/// A Timer is an abstraction of a clock to be used for performance
84/// monitoring. It is intended to allow for many implementations.
85/// The underlying clock implementation determines the meaning of an
86/// interval value. For example, a DurationTimer uses the standard
87/// rust Duration type, which returns wall-clock time.
88
89pub trait Timer {
90 /// The start() method starts a timing interval. It may be called
91 /// multiple times on a single instance. The last invocation of
92 /// the start method overrides any previous calls.
93
94 fn start(&mut self); // start or restart a timer
95
96 /// The finish() method is used at the end of a sample interval.
97 /// It returns the interval time in ticks and also starts a new
98 /// interval, since the restart cost is nearly zero. Thus, finish()
99 /// can be called multiple times after a start() invocation to return
100 /// the times for a sequence of events. If a more precise timing is
101 /// required, the user must invoke start().
102
103 fn finish(&mut self) -> i64; // get the elapsed time and set a new start time
104
105 /// hz() returns the frequency of the underlying clock.
106
107 fn hz(&self) -> u128; // get the clock hz
108}
109
110/// DurationTimer uses the Rust standard time struct Duration to
111/// measure time intervals. This timer thus returns wall-clock time.
112/// It currently works in units of nanoseconds.
113
114#[derive(Clone)]
115pub struct DurationTimer {
116 start: Instant,
117 previous: u128,
118}
119
120impl Timer for DurationTimer {
121 fn start(&mut self) {
122 self.start = Instant::now();
123 self.previous = 0;
124 }
125
126 // Get the current elapsed time and subtract
127 // "previous" from it to get the time since the
128 // last "finish" call. Then save this current
129 // time as the new "previous".
130
131 fn finish(&mut self) -> i64 {
132 let end_time = self.start.elapsed().as_nanos();
133 let result = end_time - self.previous;
134 self.previous = end_time;
135
136 std::cmp::min(result, i64::MAX as u128) as i64
137 }
138
139 // We read the clock in nanoseconds currently.
140
141 fn hz(&self) -> u128 {
142 1_000_000_000
143 }
144}
145
146impl DurationTimer {
147 pub fn new() -> DurationTimer {
148 let start = Instant::now();
149 let previous = 0;
150
151 DurationTimer { start, previous }
152 }
153
154 pub fn new_box() -> TimerBox {
155 let timer = DurationTimer::new();
156
157 timer_box!(timer)
158 }
159}
160
161impl Default for DurationTimer {
162 fn default() -> Self {
163 Self::new()
164 }
165}
166
167/// SimpleClock can be implemented for platform-specific clocks.
168/// The instances can then be wrapped in a ClockTimer instance.
169
170pub trait SimpleClock {
171 /// Returns the current "time" in ticks, for whatever the
172 /// time means for the clock being used. A cycle counter
173 /// will just return the current cycle count, for example.
174
175 fn get_time(&mut self) -> u128;
176
177 /// Returns the frequency of the clock.
178
179 fn hz(&self) -> u128;
180}
181
182/// The ClockTimer type is a wrapper class for platform-specific
183/// clocks. It can be used for cycle counters and execution
184/// time clocks, for example.
185
186#[derive(Clone)]
187pub struct ClockTimer {
188 start: u128,
189 clock: Rc<RefCell<dyn SimpleClock>>,
190 hz: u128,
191}
192
193impl Timer for ClockTimer {
194 fn start(&mut self) {
195 self.start = self.clock.borrow_mut().get_time();
196 }
197
198 fn finish(&mut self) -> i64 {
199 let end_time = self.clock.borrow_mut().get_time();
200 let ticks = end_time - self.start;
201 self.start = end_time;
202
203 ticks as i64
204 }
205
206 fn hz(&self) -> u128 {
207 self.hz
208 }
209}
210
211impl ClockTimer {
212 pub fn new(clock: Rc<RefCell<dyn SimpleClock>>) -> ClockTimer {
213 let start = clock.borrow_mut().get_time();
214 let hz = clock.borrow().hz();
215
216 ClockTimer { start, clock, hz }
217 }
218
219 pub fn new_box(clock: Rc<RefCell<dyn SimpleClock>>) -> TimerBox {
220 let timer = ClockTimer::new(clock);
221
222 timer_box!(timer)
223 }
224}
225
226/// Converts a TimerBox instance into the shareable form,
227/// currently `Rc<RefCell<dyn Timer>>`.
228
229#[macro_export]
230macro_rules! timer_box { ($x:expr) => { Rc::from(RefCell::new($x)) } }
231
232/// Converts a TimerBox into a Timer reference.
233
234#[macro_export]
235macro_rules! timer { ($x:expr) => { &*$x.borrow() } }
236
237/// Converts a TimerBox into a mutable Timer reference.
238
239#[macro_export]
240macro_rules! timer_mut { ($x:expr) => { &mut *$x.borrow_mut() } }
241
242#[cfg(test)]
243pub mod tests {
244 use super::*;
245 use std::thread::sleep;
246 use std::time::Duration;
247
248 fn simple_duration_test() {
249 let mut clock = DurationTimer::new();
250 let seconds = 1;
251 let sleep_time = Duration::new(seconds, 0);
252 let base_interval = seconds as i64 * clock.hz() as i64;
253
254 clock.start();
255
256 for _i in 1..10 {
257 sleep(sleep_time);
258
259 let interval = clock.finish();
260
261 assert!(interval >= base_interval);
262 assert!(interval < base_interval + (base_interval / 20));
263 }
264 }
265
266 pub struct TestSimpleClock {
267 pub current: u128,
268 pub increment: u128,
269 }
270
271 impl SimpleClock for TestSimpleClock {
272 fn get_time(&mut self) -> u128 {
273 let result = self.current;
274
275 self.current = self.current + self.increment;
276 result
277 }
278
279 fn hz(&self) -> u128 {
280 1_000_000_000
281 }
282 }
283
284 pub fn simple_test_clock() {
285 let current = 0;
286 let increment = 1500;
287 let simple_clock = timer_box!(TestSimpleClock { current, increment });
288 let clock = ClockTimer::new_box(simple_clock);
289 let clock = timer_mut!(clock);
290
291 // Creating the clock invokes get_time, so the increment in the
292 // test clock increases. Keep ours in sync with it.
293
294 assert!(clock.hz() == 1_000_000_000);
295
296 clock.start();
297
298 for _i in 1..5 {
299 let interval = clock.finish();
300 assert!(interval == increment as i64);
301 }
302 }
303
304 // This is an example implementation of the SimpleClock
305 // trait. It simple returns a series of time values
306 // incrementing by one in size per invocation.
307
308 struct ExampleClock {
309 current_time: u128,
310 hz: u128,
311 }
312
313 impl ExampleClock {
314 fn new(start_time: u128, hz: u128) -> ExampleClock {
315 let current_time = start_time;
316
317 ExampleClock { current_time, hz }
318 }
319 }
320
321 impl SimpleClock for ExampleClock {
322 fn get_time(&mut self) -> u128 {
323 self.current_time += 1;
324 self.current_time
325 }
326
327 fn hz(&self) -> u128 {
328 self.hz
329 }
330 }
331
332 fn sample_usage() {
333 let start_time = 1;
334 let hz = 1_000_000_000;
335 let mut clock = ExampleClock::new(start_time, hz);
336
337 // Get a few time values.
338
339 for i in 1..100 {
340 let time = clock.get_time();
341
342 assert!(time == start_time + i);
343 }
344
345 assert!(clock.hz() == hz);
346 }
347
348 fn simple_default_test() {
349 let mut timer = DurationTimer::default();
350
351 for _i in 1..100 {
352 let nanoseconds = timer.finish();
353 assert!(nanoseconds >= 0)
354 }
355 }
356
357 #[test]
358 pub fn run_tests() {
359 simple_duration_test();
360 simple_default_test ();
361 simple_test_clock ();
362 sample_usage ();
363 }
364}