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