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}