Skip to main content

qubit_clock/meter/
time_meter.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10//! Millisecond-precision time meter implementation.
11//!
12//! This module provides [`TimeMeter`], a simple yet powerful time
13//! measurement tool with millisecond precision. For nanosecond precision,
14//! use [`NanoTimeMeter`](super::NanoTimeMeter).
15//!
16
17use crate::meter::format::{
18    format_duration_millis,
19    format_speed,
20};
21use crate::{
22    Clock,
23    MonotonicClock,
24};
25use chrono::Duration;
26
27/// A time meter for measuring elapsed time with millisecond precision.
28///
29/// This meter provides a simple and powerful tool for time measurement
30/// with the following features:
31///
32/// - **Flexible clock source**: Supports any clock implementing `Clock`
33///   trait via generic parameter
34/// - **High precision**: Uses `MonotonicClock` by default, based on
35///   `Instant`
36/// - **Easy to use**: Provides simple start/stop interface
37/// - **Multiple output formats**: Supports milliseconds, seconds, minutes,
38///   and human-readable format
39/// - **Speed calculation**: Provides per-second and per-minute speed
40///   calculation
41/// - **Test-friendly**: Supports injecting `MockClock` for unit testing
42///
43/// # Design Philosophy
44///
45/// `TimeMeter` uses dependency injection pattern through the `Clock`
46/// trait. This design brings the following benefits:
47///
48/// 1. **Production reliability**: Uses `MonotonicClock` by default,
49///    ensuring time measurement is not affected by system time adjustments
50/// 2. **Test controllability**: Can inject `MockClock` for deterministic
51///    time testing
52/// 3. **Extensibility**: Can implement custom `Clock` to meet special
53///    requirements
54/// 4. **Compatibility**: Fully compatible with standard Java time API
55///
56/// # Default Clock Selection
57///
58/// If no clock is specified, this meter uses `MonotonicClock` as the
59/// default clock instead of system clock. This is because:
60///
61/// - `MonotonicClock` is based on `Instant`, providing monotonically
62///   increasing time
63/// - Not affected by system time adjustments (e.g., NTP sync, manual
64///   settings)
65/// - More suitable for performance measurement and benchmarking scenarios
66/// - Provides more stable and reliable results in most use cases
67///
68/// # Thread Safety
69///
70/// This type is not thread-safe. If you need to use it in a
71/// multi-threaded environment, you should create separate instances for
72/// each thread or use external synchronization mechanisms.
73///
74/// # Examples
75///
76/// ```
77/// use qubit_clock::meter::TimeMeter;
78/// use std::thread;
79/// use std::time::Duration as StdDuration;
80///
81/// // Basic usage
82/// let mut meter = TimeMeter::new();
83/// meter.start();
84/// thread::sleep(StdDuration::from_millis(100));
85/// meter.stop();
86/// println!("Elapsed: {}", meter.readable_duration());
87///
88/// // Auto-start
89/// let mut meter = TimeMeter::start_now();
90/// thread::sleep(StdDuration::from_millis(50));
91/// meter.stop();
92///
93/// // Real-time monitoring (without calling stop)
94/// let mut meter = TimeMeter::start_now();
95/// for _ in 0..5 {
96///     thread::sleep(StdDuration::from_millis(10));
97///     println!("Running: {}", meter.readable_duration());
98/// }
99/// ```
100///
101pub struct TimeMeter<C: Clock> {
102    /// The clock used by this meter.
103    clock: C,
104    /// Start timestamp in milliseconds. `None` means not started.
105    start_time: Option<i64>,
106    /// End timestamp in milliseconds. `None` means not stopped.
107    end_time: Option<i64>,
108}
109
110impl<C: Clock> TimeMeter<C> {
111    /// Creates a new time meter with the specified clock.
112    ///
113    /// # Arguments
114    ///
115    /// * `clock` - The clock to use for time measurement
116    ///
117    /// # Returns
118    ///
119    /// A new `TimeMeter` instance
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// use qubit_clock::{MonotonicClock, meter::TimeMeter};
125    ///
126    /// let clock = MonotonicClock::new();
127    /// let meter = TimeMeter::with_clock(clock);
128    /// ```
129    #[inline]
130    pub fn with_clock(clock: C) -> Self {
131        TimeMeter {
132            clock,
133            start_time: None,
134            end_time: None,
135        }
136    }
137
138    /// Creates a new time meter with the specified clock and starts it
139    /// immediately.
140    ///
141    /// # Arguments
142    ///
143    /// * `clock` - The clock to use for time measurement
144    ///
145    /// # Returns
146    ///
147    /// A new `TimeMeter` instance that has already been started
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use qubit_clock::{MonotonicClock, meter::TimeMeter};
153    ///
154    /// let clock = MonotonicClock::new();
155    /// let meter = TimeMeter::with_clock_started(clock);
156    /// ```
157    #[inline]
158    pub fn with_clock_started(clock: C) -> Self {
159        let mut meter = Self::with_clock(clock);
160        meter.start();
161        meter
162    }
163
164    /// Starts this meter.
165    ///
166    /// Records the current time as the start timestamp. If the meter has
167    /// already been started, this operation will restart timing.
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// use qubit_clock::meter::TimeMeter;
173    ///
174    /// let mut meter = TimeMeter::new();
175    /// meter.start();
176    /// ```
177    #[inline]
178    pub fn start(&mut self) {
179        self.start_time = Some(self.clock.millis());
180        self.end_time = None;
181    }
182
183    /// Stops this meter.
184    ///
185    /// Records the current time as the end timestamp. After calling this
186    /// method, `duration()` will return a fixed time interval until
187    /// `start()` or `reset()` is called again.
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// use qubit_clock::meter::TimeMeter;
193    ///
194    /// let mut meter = TimeMeter::start_now();
195    /// // Do some work
196    /// meter.stop();
197    /// ```
198    #[inline]
199    pub fn stop(&mut self) {
200        self.end_time = Some(self.clock.millis());
201    }
202
203    /// Resets this meter.
204    ///
205    /// Clears the start and end timestamps, restoring the meter to its
206    /// initial state. After reset, you need to call `start()` again to
207    /// begin a new time measurement.
208    ///
209    /// # Examples
210    ///
211    /// ```
212    /// use qubit_clock::meter::TimeMeter;
213    ///
214    /// let mut meter = TimeMeter::start_now();
215    /// // Do some work
216    /// meter.stop();
217    /// meter.reset();
218    /// ```
219    #[inline]
220    pub fn reset(&mut self) {
221        self.start_time = None;
222        self.end_time = None;
223    }
224
225    /// Resets and immediately starts this meter.
226    ///
227    /// This is equivalent to calling `reset()` followed by `start()`.
228    ///
229    /// # Examples
230    ///
231    /// ```
232    /// use qubit_clock::meter::TimeMeter;
233    ///
234    /// let mut meter = TimeMeter::start_now();
235    /// // Do some work
236    /// meter.restart();
237    /// // Do more work
238    /// ```
239    #[inline]
240    pub fn restart(&mut self) {
241        self.reset();
242        self.start();
243    }
244
245    /// Returns the elapsed duration in milliseconds.
246    ///
247    /// If the meter has been stopped (by calling `stop()`), returns the
248    /// time interval from start to stop. If the meter has not been
249    /// stopped, returns the time interval from start to the current
250    /// moment.
251    ///
252    /// If the meter has not been started (by calling `start()`), returns
253    /// 0.
254    ///
255    /// # Returns
256    ///
257    /// The elapsed duration in milliseconds
258    ///
259    /// # Examples
260    ///
261    /// ```
262    /// use qubit_clock::meter::TimeMeter;
263    /// use std::thread;
264    /// use std::time::Duration;
265    ///
266    /// let mut meter = TimeMeter::start_now();
267    /// thread::sleep(Duration::from_millis(100));
268    /// assert!(meter.millis() >= 100);
269    /// ```
270    #[inline]
271    pub fn millis(&self) -> i64 {
272        let start = match self.start_time {
273            Some(t) => t,
274            None => return 0,
275        };
276        let end = self.end_time.unwrap_or_else(|| self.clock.millis());
277        end.saturating_sub(start)
278    }
279
280    /// Returns the elapsed duration in seconds.
281    ///
282    /// This method is based on the result of `millis()`, converting
283    /// milliseconds to seconds (rounded down).
284    ///
285    /// # Returns
286    ///
287    /// The elapsed duration in seconds
288    ///
289    /// # Examples
290    ///
291    /// ```
292    /// use qubit_clock::meter::TimeMeter;
293    /// use std::thread;
294    /// use std::time::Duration;
295    ///
296    /// let mut meter = TimeMeter::start_now();
297    /// thread::sleep(Duration::from_secs(2));
298    /// assert!(meter.seconds() >= 2);
299    /// ```
300    #[inline]
301    pub fn seconds(&self) -> i64 {
302        self.millis() / 1000
303    }
304
305    /// Returns the elapsed duration in minutes.
306    ///
307    /// This method is based on the result of `millis()`, converting
308    /// milliseconds to minutes (rounded down).
309    ///
310    /// # Returns
311    ///
312    /// The elapsed duration in minutes
313    ///
314    /// # Examples
315    ///
316    /// ```
317    /// use qubit_clock::meter::TimeMeter;
318    ///
319    /// let mut meter = TimeMeter::new();
320    /// meter.start();
321    /// // Simulate 2 minutes
322    /// meter.stop();
323    /// // In real usage, this would be >= 2 after 2 minutes
324    /// ```
325    #[inline]
326    pub fn minutes(&self) -> i64 {
327        self.millis() / 60000
328    }
329
330    /// Returns the elapsed duration as a `Duration` object.
331    ///
332    /// If the meter has been stopped (by calling `stop()`), returns the
333    /// time interval from start to stop. If the meter has not been
334    /// stopped, returns the time interval from start to the current
335    /// moment.
336    ///
337    /// If the meter has not been started (by calling `start()`), returns
338    /// a zero duration.
339    ///
340    /// # Returns
341    ///
342    /// The elapsed duration as a `Duration` object (millisecond precision)
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// use qubit_clock::meter::TimeMeter;
348    ///
349    /// let mut meter = TimeMeter::start_now();
350    /// let duration = meter.duration();
351    /// ```
352    #[inline]
353    pub fn duration(&self) -> Duration {
354        Duration::milliseconds(self.millis().max(-i64::MAX))
355    }
356
357    /// Returns a human-readable string representation of the elapsed
358    /// duration.
359    ///
360    /// Formats the duration into an easy-to-read string, such as
361    /// "1h 23m 45s" or "2.5s".
362    ///
363    /// # Returns
364    ///
365    /// A human-readable string representation of the duration
366    ///
367    /// # Examples
368    ///
369    /// ```
370    /// use qubit_clock::meter::TimeMeter;
371    ///
372    /// let mut meter = TimeMeter::start_now();
373    /// // Do some work
374    /// meter.stop();
375    /// println!("Elapsed: {}", meter.readable_duration());
376    /// ```
377    #[inline]
378    pub fn readable_duration(&self) -> String {
379        format_duration_millis(self.millis())
380    }
381
382    /// Calculates the per-second speed for a given count.
383    ///
384    /// Computes the average count processed per second during the elapsed
385    /// time. Useful for performance monitoring and speed analysis.
386    ///
387    /// # Arguments
388    ///
389    /// * `count` - The count value to calculate speed for
390    ///
391    /// # Returns
392    ///
393    /// The per-second speed, or `None` if the elapsed time is zero
394    ///
395    /// # Examples
396    ///
397    /// ```
398    /// use qubit_clock::meter::TimeMeter;
399    /// use std::thread;
400    /// use std::time::Duration;
401    ///
402    /// let mut meter = TimeMeter::start_now();
403    /// thread::sleep(Duration::from_secs(1));
404    /// meter.stop();
405    /// if let Some(speed) = meter.speed_per_second(1000) {
406    ///     println!("Speed: {:.2} items/s", speed);
407    /// }
408    /// ```
409    #[inline]
410    pub fn speed_per_second(&self, count: usize) -> Option<f64> {
411        let elapsed_millis = self.millis();
412        if elapsed_millis <= 0 {
413            None
414        } else {
415            Some((count as f64 * 1000.0) / elapsed_millis as f64)
416        }
417    }
418
419    /// Calculates the per-minute speed for a given count.
420    ///
421    /// Computes the average count processed per minute during the elapsed
422    /// time. Useful for performance monitoring and speed analysis.
423    ///
424    /// # Arguments
425    ///
426    /// * `count` - The count value to calculate speed for
427    ///
428    /// # Returns
429    ///
430    /// The per-minute speed, or `None` if the elapsed time is zero
431    ///
432    /// # Examples
433    ///
434    /// ```
435    /// use qubit_clock::meter::TimeMeter;
436    /// use std::thread;
437    /// use std::time::Duration;
438    ///
439    /// let mut meter = TimeMeter::start_now();
440    /// thread::sleep(Duration::from_secs(1));
441    /// meter.stop();
442    /// if let Some(speed) = meter.speed_per_minute(1000) {
443    ///     println!("Speed: {:.2} items/m", speed);
444    /// }
445    /// ```
446    #[inline]
447    pub fn speed_per_minute(&self, count: usize) -> Option<f64> {
448        let elapsed_millis = self.millis();
449        if elapsed_millis <= 0 {
450            None
451        } else {
452            Some((count as f64 * 60_000.0) / elapsed_millis as f64)
453        }
454    }
455
456    /// Returns a formatted string of the per-second speed for a given
457    /// count.
458    ///
459    /// # Arguments
460    ///
461    /// * `count` - The count value to calculate speed for
462    ///
463    /// # Returns
464    ///
465    /// A string in the format "{speed}/s", or "N/A" if the elapsed time
466    /// is zero
467    ///
468    /// # Examples
469    ///
470    /// ```
471    /// use qubit_clock::meter::TimeMeter;
472    ///
473    /// let mut meter = TimeMeter::start_now();
474    /// // Do some work
475    /// meter.stop();
476    /// println!("Speed: {}", meter.formatted_speed_per_second(1000));
477    /// ```
478    #[inline]
479    pub fn formatted_speed_per_second(&self, count: usize) -> String {
480        match self.speed_per_second(count) {
481            Some(speed) => format_speed(speed, "/s"),
482            None => "N/A".to_string(),
483        }
484    }
485
486    /// Returns a formatted string of the per-minute speed for a given
487    /// count.
488    ///
489    /// # Arguments
490    ///
491    /// * `count` - The count value to calculate speed for
492    ///
493    /// # Returns
494    ///
495    /// A string in the format "{speed}/m", or "N/A" if the elapsed time
496    /// is zero
497    ///
498    /// # Examples
499    ///
500    /// ```
501    /// use qubit_clock::meter::TimeMeter;
502    ///
503    /// let mut meter = TimeMeter::start_now();
504    /// // Do some work
505    /// meter.stop();
506    /// println!("Speed: {}", meter.formatted_speed_per_minute(1000));
507    /// ```
508    #[inline]
509    pub fn formatted_speed_per_minute(&self, count: usize) -> String {
510        match self.speed_per_minute(count) {
511            Some(speed) => format_speed(speed, "/m"),
512            None => "N/A".to_string(),
513        }
514    }
515
516    /// Checks if the meter is currently running.
517    ///
518    /// # Returns
519    ///
520    /// `true` if the meter has been started but not stopped, `false`
521    /// otherwise
522    ///
523    /// # Examples
524    ///
525    /// ```
526    /// use qubit_clock::meter::TimeMeter;
527    ///
528    /// let mut meter = TimeMeter::new();
529    /// assert!(!meter.is_running());
530    /// meter.start();
531    /// assert!(meter.is_running());
532    /// meter.stop();
533    /// assert!(!meter.is_running());
534    /// ```
535    #[inline]
536    pub fn is_running(&self) -> bool {
537        self.start_time.is_some() && self.end_time.is_none()
538    }
539
540    /// Checks if the meter has been stopped.
541    ///
542    /// # Returns
543    ///
544    /// `true` if the meter has been stopped, `false` otherwise
545    ///
546    /// # Examples
547    ///
548    /// ```
549    /// use qubit_clock::meter::TimeMeter;
550    ///
551    /// let mut meter = TimeMeter::start_now();
552    /// assert!(!meter.is_stopped());
553    /// meter.stop();
554    /// assert!(meter.is_stopped());
555    /// ```
556    #[inline]
557    pub fn is_stopped(&self) -> bool {
558        self.end_time.is_some()
559    }
560
561    /// Returns a reference to the clock used by this meter.
562    ///
563    /// # Returns
564    ///
565    /// A reference to the clock
566    ///
567    /// # Examples
568    ///
569    /// ```
570    /// use qubit_clock::meter::TimeMeter;
571    ///
572    /// let meter = TimeMeter::new();
573    /// let clock = meter.clock();
574    /// ```
575    #[inline]
576    pub fn clock(&self) -> &C {
577        &self.clock
578    }
579
580    /// Returns a mutable reference to the clock used by this meter.
581    ///
582    /// # Returns
583    ///
584    /// A mutable reference to the clock
585    ///
586    /// # Examples
587    ///
588    /// ```
589    /// use qubit_clock::meter::TimeMeter;
590    ///
591    /// let mut meter = TimeMeter::new();
592    /// let clock = meter.clock_mut();
593    /// ```
594    #[inline]
595    pub fn clock_mut(&mut self) -> &mut C {
596        &mut self.clock
597    }
598}
599
600impl TimeMeter<MonotonicClock> {
601    /// Creates a new time meter using the default `MonotonicClock`.
602    ///
603    /// The default clock uses `MonotonicClock`, which is based on
604    /// `Instant` and is not affected by system time adjustments, making
605    /// it more suitable for time measurement.
606    ///
607    /// # Returns
608    ///
609    /// A new `TimeMeter` instance
610    ///
611    /// # Examples
612    ///
613    /// ```
614    /// use qubit_clock::meter::TimeMeter;
615    ///
616    /// let meter = TimeMeter::new();
617    /// ```
618    #[inline]
619    pub fn new() -> Self {
620        Self::with_clock(MonotonicClock::new())
621    }
622
623    /// Creates a new time meter using the default `MonotonicClock` and
624    /// starts it immediately.
625    ///
626    /// # Returns
627    ///
628    /// A new `TimeMeter` instance that has already been started
629    ///
630    /// # Examples
631    ///
632    /// ```
633    /// use qubit_clock::meter::TimeMeter;
634    ///
635    /// let meter = TimeMeter::start_now();
636    /// ```
637    #[inline]
638    pub fn start_now() -> Self {
639        Self::with_clock_started(MonotonicClock::new())
640    }
641}
642
643impl Default for TimeMeter<MonotonicClock> {
644    #[inline]
645    fn default() -> Self {
646        Self::new()
647    }
648}