prism3_clock/meter/time_meter.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025.
4 * 3-Prism 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 prism3_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 prism3_clock::{MonotonicClock, meter::TimeMeter};
124 ///
125 /// let clock = MonotonicClock::new();
126 /// let meter = TimeMeter::with_clock(clock);
127 /// ```
128 pub fn with_clock(clock: C) -> Self {
129 TimeMeter {
130 clock,
131 start_time: None,
132 end_time: None,
133 }
134 }
135
136 /// Creates a new time meter with the specified clock and starts it
137 /// immediately.
138 ///
139 /// # Arguments
140 ///
141 /// * `clock` - The clock to use for time measurement
142 ///
143 /// # Returns
144 ///
145 /// A new `TimeMeter` instance that has already been started
146 ///
147 /// # Examples
148 ///
149 /// ```
150 /// use prism3_clock::{MonotonicClock, meter::TimeMeter};
151 ///
152 /// let clock = MonotonicClock::new();
153 /// let meter = TimeMeter::with_clock_started(clock);
154 /// ```
155 pub fn with_clock_started(clock: C) -> Self {
156 let mut meter = Self::with_clock(clock);
157 meter.start();
158 meter
159 }
160
161 /// Starts this meter.
162 ///
163 /// Records the current time as the start timestamp. If the meter has
164 /// already been started, this operation will restart timing.
165 ///
166 /// # Examples
167 ///
168 /// ```
169 /// use prism3_clock::meter::TimeMeter;
170 ///
171 /// let mut meter = TimeMeter::new();
172 /// meter.start();
173 /// ```
174 pub fn start(&mut self) {
175 self.start_time = Some(self.clock.millis());
176 self.end_time = None;
177 }
178
179 /// Stops this meter.
180 ///
181 /// Records the current time as the end timestamp. After calling this
182 /// method, `duration()` will return a fixed time interval until
183 /// `start()` or `reset()` is called again.
184 ///
185 /// # Examples
186 ///
187 /// ```
188 /// use prism3_clock::meter::TimeMeter;
189 ///
190 /// let mut meter = TimeMeter::start_now();
191 /// // Do some work
192 /// meter.stop();
193 /// ```
194 pub fn stop(&mut self) {
195 self.end_time = Some(self.clock.millis());
196 }
197
198 /// Resets this meter.
199 ///
200 /// Clears the start and end timestamps, restoring the meter to its
201 /// initial state. After reset, you need to call `start()` again to
202 /// begin a new time measurement.
203 ///
204 /// # Examples
205 ///
206 /// ```
207 /// use prism3_clock::meter::TimeMeter;
208 ///
209 /// let mut meter = TimeMeter::start_now();
210 /// // Do some work
211 /// meter.stop();
212 /// meter.reset();
213 /// ```
214 pub fn reset(&mut self) {
215 self.start_time = None;
216 self.end_time = None;
217 }
218
219 /// Resets and immediately starts this meter.
220 ///
221 /// This is equivalent to calling `reset()` followed by `start()`.
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use prism3_clock::meter::TimeMeter;
227 ///
228 /// let mut meter = TimeMeter::start_now();
229 /// // Do some work
230 /// meter.restart();
231 /// // Do more work
232 /// ```
233 pub fn restart(&mut self) {
234 self.reset();
235 self.start();
236 }
237
238 /// Returns the elapsed duration in milliseconds.
239 ///
240 /// If the meter has been stopped (by calling `stop()`), returns the
241 /// time interval from start to stop. If the meter has not been
242 /// stopped, returns the time interval from start to the current
243 /// moment.
244 ///
245 /// If the meter has not been started (by calling `start()`), returns
246 /// 0.
247 ///
248 /// # Returns
249 ///
250 /// The elapsed duration in milliseconds
251 ///
252 /// # Examples
253 ///
254 /// ```
255 /// use prism3_clock::meter::TimeMeter;
256 /// use std::thread;
257 /// use std::time::Duration;
258 ///
259 /// let mut meter = TimeMeter::start_now();
260 /// thread::sleep(Duration::from_millis(100));
261 /// assert!(meter.millis() >= 100);
262 /// ```
263 pub fn millis(&self) -> i64 {
264 let start = match self.start_time {
265 Some(t) => t,
266 None => return 0,
267 };
268 let end = self.end_time.unwrap_or_else(|| self.clock.millis());
269 end - start
270 }
271
272 /// Returns the elapsed duration in seconds.
273 ///
274 /// This method is based on the result of `millis()`, converting
275 /// milliseconds to seconds (rounded down).
276 ///
277 /// # Returns
278 ///
279 /// The elapsed duration in seconds
280 ///
281 /// # Examples
282 ///
283 /// ```
284 /// use prism3_clock::meter::TimeMeter;
285 /// use std::thread;
286 /// use std::time::Duration;
287 ///
288 /// let mut meter = TimeMeter::start_now();
289 /// thread::sleep(Duration::from_secs(2));
290 /// assert!(meter.seconds() >= 2);
291 /// ```
292 pub fn seconds(&self) -> i64 {
293 self.millis() / 1000
294 }
295
296 /// Returns the elapsed duration in minutes.
297 ///
298 /// This method is based on the result of `millis()`, converting
299 /// milliseconds to minutes (rounded down).
300 ///
301 /// # Returns
302 ///
303 /// The elapsed duration in minutes
304 ///
305 /// # Examples
306 ///
307 /// ```
308 /// use prism3_clock::meter::TimeMeter;
309 ///
310 /// let mut meter = TimeMeter::new();
311 /// meter.start();
312 /// // Simulate 2 minutes
313 /// meter.stop();
314 /// // In real usage, this would be >= 2 after 2 minutes
315 /// ```
316 pub fn minutes(&self) -> i64 {
317 self.millis() / 60000
318 }
319
320 /// Returns the elapsed duration as a `Duration` object.
321 ///
322 /// If the meter has been stopped (by calling `stop()`), returns the
323 /// time interval from start to stop. If the meter has not been
324 /// stopped, returns the time interval from start to the current
325 /// moment.
326 ///
327 /// If the meter has not been started (by calling `start()`), returns
328 /// a zero duration.
329 ///
330 /// # Returns
331 ///
332 /// The elapsed duration as a `Duration` object (millisecond precision)
333 ///
334 /// # Examples
335 ///
336 /// ```
337 /// use prism3_clock::meter::TimeMeter;
338 ///
339 /// let mut meter = TimeMeter::start_now();
340 /// let duration = meter.duration();
341 /// ```
342 pub fn duration(&self) -> Duration {
343 Duration::milliseconds(self.millis())
344 }
345
346 /// Returns a human-readable string representation of the elapsed
347 /// duration.
348 ///
349 /// Formats the duration into an easy-to-read string, such as
350 /// "1h 23m 45s" or "2.5s".
351 ///
352 /// # Returns
353 ///
354 /// A human-readable string representation of the duration
355 ///
356 /// # Examples
357 ///
358 /// ```
359 /// use prism3_clock::meter::TimeMeter;
360 ///
361 /// let mut meter = TimeMeter::start_now();
362 /// // Do some work
363 /// meter.stop();
364 /// println!("Elapsed: {}", meter.readable_duration());
365 /// ```
366 pub fn readable_duration(&self) -> String {
367 format_duration_millis(self.millis())
368 }
369
370 /// Calculates the per-second speed for a given count.
371 ///
372 /// Computes the average count processed per second during the elapsed
373 /// time. Useful for performance monitoring and speed analysis.
374 ///
375 /// # Arguments
376 ///
377 /// * `count` - The count value to calculate speed for
378 ///
379 /// # Returns
380 ///
381 /// The per-second speed, or `None` if the elapsed time is zero
382 ///
383 /// # Examples
384 ///
385 /// ```
386 /// use prism3_clock::meter::TimeMeter;
387 /// use std::thread;
388 /// use std::time::Duration;
389 ///
390 /// let mut meter = TimeMeter::start_now();
391 /// thread::sleep(Duration::from_secs(1));
392 /// meter.stop();
393 /// if let Some(speed) = meter.speed_per_second(1000) {
394 /// println!("Speed: {:.2} items/s", speed);
395 /// }
396 /// ```
397 pub fn speed_per_second(&self, count: usize) -> Option<f64> {
398 let seconds = self.seconds();
399 if seconds == 0 {
400 None
401 } else {
402 Some(count as f64 / seconds as f64)
403 }
404 }
405
406 /// Calculates the per-minute speed for a given count.
407 ///
408 /// Computes the average count processed per minute during the elapsed
409 /// time. Useful for performance monitoring and speed analysis.
410 ///
411 /// # Arguments
412 ///
413 /// * `count` - The count value to calculate speed for
414 ///
415 /// # Returns
416 ///
417 /// The per-minute speed, or `None` if the elapsed time is zero
418 ///
419 /// # Examples
420 ///
421 /// ```
422 /// use prism3_clock::meter::TimeMeter;
423 /// use std::thread;
424 /// use std::time::Duration;
425 ///
426 /// let mut meter = TimeMeter::start_now();
427 /// thread::sleep(Duration::from_secs(1));
428 /// meter.stop();
429 /// if let Some(speed) = meter.speed_per_minute(1000) {
430 /// println!("Speed: {:.2} items/m", speed);
431 /// }
432 /// ```
433 pub fn speed_per_minute(&self, count: usize) -> Option<f64> {
434 let seconds = self.seconds();
435 if seconds == 0 {
436 None
437 } else {
438 Some((count as f64 / seconds as f64) * 60.0)
439 }
440 }
441
442 /// Returns a formatted string of the per-second speed for a given
443 /// count.
444 ///
445 /// # Arguments
446 ///
447 /// * `count` - The count value to calculate speed for
448 ///
449 /// # Returns
450 ///
451 /// A string in the format "{speed}/s", or "N/A" if the elapsed time
452 /// is zero
453 ///
454 /// # Examples
455 ///
456 /// ```
457 /// use prism3_clock::meter::TimeMeter;
458 ///
459 /// let mut meter = TimeMeter::start_now();
460 /// // Do some work
461 /// meter.stop();
462 /// println!("Speed: {}", meter.formatted_speed_per_second(1000));
463 /// ```
464 pub fn formatted_speed_per_second(&self, count: usize) -> String {
465 match self.speed_per_second(count) {
466 Some(speed) => format_speed(speed, "/s"),
467 None => "N/A".to_string(),
468 }
469 }
470
471 /// Returns a formatted string of the per-minute speed for a given
472 /// count.
473 ///
474 /// # Arguments
475 ///
476 /// * `count` - The count value to calculate speed for
477 ///
478 /// # Returns
479 ///
480 /// A string in the format "{speed}/m", or "N/A" if the elapsed time
481 /// is zero
482 ///
483 /// # Examples
484 ///
485 /// ```
486 /// use prism3_clock::meter::TimeMeter;
487 ///
488 /// let mut meter = TimeMeter::start_now();
489 /// // Do some work
490 /// meter.stop();
491 /// println!("Speed: {}", meter.formatted_speed_per_minute(1000));
492 /// ```
493 pub fn formatted_speed_per_minute(&self, count: usize) -> String {
494 match self.speed_per_minute(count) {
495 Some(speed) => format_speed(speed, "/m"),
496 None => "N/A".to_string(),
497 }
498 }
499
500 /// Checks if the meter is currently running.
501 ///
502 /// # Returns
503 ///
504 /// `true` if the meter has been started but not stopped, `false`
505 /// otherwise
506 ///
507 /// # Examples
508 ///
509 /// ```
510 /// use prism3_clock::meter::TimeMeter;
511 ///
512 /// let mut meter = TimeMeter::new();
513 /// assert!(!meter.is_running());
514 /// meter.start();
515 /// assert!(meter.is_running());
516 /// meter.stop();
517 /// assert!(!meter.is_running());
518 /// ```
519 pub fn is_running(&self) -> bool {
520 self.start_time.is_some() && self.end_time.is_none()
521 }
522
523 /// Checks if the meter has been stopped.
524 ///
525 /// # Returns
526 ///
527 /// `true` if the meter has been stopped, `false` otherwise
528 ///
529 /// # Examples
530 ///
531 /// ```
532 /// use prism3_clock::meter::TimeMeter;
533 ///
534 /// let mut meter = TimeMeter::start_now();
535 /// assert!(!meter.is_stopped());
536 /// meter.stop();
537 /// assert!(meter.is_stopped());
538 /// ```
539 pub fn is_stopped(&self) -> bool {
540 self.end_time.is_some()
541 }
542
543 /// Returns a reference to the clock used by this meter.
544 ///
545 /// # Returns
546 ///
547 /// A reference to the clock
548 ///
549 /// # Examples
550 ///
551 /// ```
552 /// use prism3_clock::meter::TimeMeter;
553 ///
554 /// let meter = TimeMeter::new();
555 /// let clock = meter.clock();
556 /// ```
557 pub fn clock(&self) -> &C {
558 &self.clock
559 }
560
561 /// Returns a mutable reference to the clock used by this meter.
562 ///
563 /// # Returns
564 ///
565 /// A mutable reference to the clock
566 ///
567 /// # Examples
568 ///
569 /// ```
570 /// use prism3_clock::meter::TimeMeter;
571 ///
572 /// let mut meter = TimeMeter::new();
573 /// let clock = meter.clock_mut();
574 /// ```
575 pub fn clock_mut(&mut self) -> &mut C {
576 &mut self.clock
577 }
578}
579
580impl TimeMeter<MonotonicClock> {
581 /// Creates a new time meter using the default `MonotonicClock`.
582 ///
583 /// The default clock uses `MonotonicClock`, which is based on
584 /// `Instant` and is not affected by system time adjustments, making
585 /// it more suitable for time measurement.
586 ///
587 /// # Returns
588 ///
589 /// A new `TimeMeter` instance
590 ///
591 /// # Examples
592 ///
593 /// ```
594 /// use prism3_clock::meter::TimeMeter;
595 ///
596 /// let meter = TimeMeter::new();
597 /// ```
598 pub fn new() -> Self {
599 Self::with_clock(MonotonicClock::new())
600 }
601
602 /// Creates a new time meter using the default `MonotonicClock` and
603 /// starts it immediately.
604 ///
605 /// # Returns
606 ///
607 /// A new `TimeMeter` instance that has already been started
608 ///
609 /// # Examples
610 ///
611 /// ```
612 /// use prism3_clock::meter::TimeMeter;
613 ///
614 /// let meter = TimeMeter::start_now();
615 /// ```
616 pub fn start_now() -> Self {
617 Self::with_clock_started(MonotonicClock::new())
618 }
619}
620
621impl Default for TimeMeter<MonotonicClock> {
622 fn default() -> Self {
623 Self::new()
624 }
625}