timelog/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
//! # Timer
//!
//! `Timer` is a Rust library for timing and logging time durations.
//!
//! ## Features
//!
//! - Create multiple named timers
//! - Start and stop timers
//! - Log elapsed time without stopping the timer
//! - Silent mode for logging without printing
//! - Convert durations to milliseconds
//! - End timers and get elapsed time
//! - Singleton instance for global timing
//!
//! ## Usage
//!
//! Create a new `Timer` instance, start timers with labels, and log or stop them as needed.
//! The library provides a simple and efficient way to measure execution time in your Rust programs.
//!
//! ## Example
//!
//! ```
//! let mut timer = Timer::new();
//! timer.time("operation");
//! // Perform some operation
//! let elapsed = timer.time_log("operation", false);
//! println!("Operation took {} ms", elapsed);
//!
//! // End a timer
//! let final_time = timer.time_end("operation");
//! println!("Final time: {} ms", final_time);
//!
//! // Use singleton instance
//! Timer::single_instance().time("global_operation");
//! // Perform global operation
//! Timer::single_instance().time_end("global_operation");
//! ```
//!
//! This library is useful for performance monitoring and optimization in Rust applications.
//! The `time_end` method allows you to stop a timer and get its final elapsed time.
//! The `single_instance` feature provides a global Timer instance for convenient timing across your application.

use std::collections::HashMap;
use std::sync::Once;
#[cfg(not(target_arch = "wasm32"))]
use std::time::{Duration, Instant};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use web_sys::{window, Performance};

/// A struct for timing and logging time durations.
///
/// `Timer` uses a `HashMap` to store multiple named timers, each associated with a label.
pub struct Timer {
    /// HashMap storing timers, where keys are labels and values are start times.
    #[cfg(not(target_arch = "wasm32"))]
    timers: HashMap<String, Instant>,
    #[cfg(target_arch = "wasm32")]
    timers: HashMap<String, f64>,
    #[cfg(target_arch = "wasm32")]
    performance: Performance,
}

impl Timer {
    /// Creates a new `Timer` instance.
    ///
    /// # Returns
    ///
    /// Returns a new `Timer` instance with an empty timer HashMap.
    pub fn new() -> Self {
        #[cfg(not(target_arch = "wasm32"))]
        return Timer {
            timers: HashMap::new(),
        };

        #[cfg(target_arch = "wasm32")]
        return Timer {
            timers: HashMap::new(),
            performance: window().unwrap().performance().unwrap(),
        };
    }

    /// Starts a new timer.
    ///
    /// # Arguments
    ///
    /// * `label` - The label for the timer.
    pub fn time(&mut self, label: &str) {
        #[cfg(not(target_arch = "wasm32"))]
        self.timers.insert(label.to_string(), Instant::now());

        #[cfg(target_arch = "wasm32")]
        self.timers
            .insert(label.to_string(), self.performance.now());
    }

    /// Logs and prints the current time of a timer without stopping it.
    ///
    /// # Arguments
    ///
    /// * `label` - The label of the timer.
    /// * `silent` - Whether to suppress printing the message.
    ///
    /// # Returns
    ///
    /// Returns the number of milliseconds the timer has been running, or 0.0 if the timer doesn't exist.
    pub fn time_log(&self, label: &str, silent: bool) -> f64 {
        #[cfg(not(target_arch = "wasm32"))]
        if let Some(start_time) = self.timers.get(label) {
            let duration = start_time.elapsed();
            let ms = Self::duration_to_ms(duration);
            if !silent {
                println!("{}: {:.3}ms", label, ms);
            }
            ms
        } else {
            eprintln!("Timer '{}' does not exist", label);
            0.0
        }

        #[cfg(target_arch = "wasm32")]
        if let Some(start_time) = self.timers.get(label) {
            let ms = self.performance.now() - start_time;
            if !silent {
                web_sys::console::log_1(&format!("{}: {:.3}ms", label, ms).into());
            }
            ms
        } else {
            web_sys::console::error_1(&format!("Timer '{}' does not exist", label).into());
            0.0
        }
    }

    /// Ends a timer and prints its runtime.
    ///
    /// # Arguments
    ///
    /// * `label` - The label of the timer.
    /// * `silent` - Whether to suppress printing the message.
    ///
    /// # Returns
    ///
    /// Returns the number of milliseconds the timer has been running, or 0.0 if the timer doesn't exist.
    pub fn time_end(&mut self, label: &str, silent: bool) -> f64 {
        #[cfg(not(target_arch = "wasm32"))]
        if let Some(start_time) = self.timers.remove(label) {
            let duration = start_time.elapsed();
            let ms = Self::duration_to_ms(duration);
            if !silent {
                println!("{}: {:.3}ms", label, ms);
            }
            ms
        } else {
            eprintln!("Timer '{}' does not exist", label);
            0.0
        }

        #[cfg(target_arch = "wasm32")]
        if let Some(start_time) = self.timers.remove(label) {
            let ms = self.performance.now() - start_time;
            if !silent {
                web_sys::console::log_1(&format!("{}: {:.3}ms", label, ms).into());
            }
            ms
        } else {
            web_sys::console::error_1(&format!("Timer '{}' does not exist", label).into());
            0.0
        }
    }

    /// Returns a global singleton instance of Timer
    ///
    /// This method implements the singleton pattern to ensure only one Timer instance
    /// exists throughout the program. It's thread-safe and lazily initialized.
    ///
    /// # Returns
    ///
    /// A static mutable reference to the global Timer instance
    ///
    /// # Safety
    ///
    /// This function uses an unsafe block because it manipulates static mutable variables.
    /// However, thread safety is guaranteed by using Once to ensure initialization happens only once.
    pub fn single_instance() -> &'static mut Timer {
        static ONCE: Once = Once::new();
        static mut SINGLETON: Option<Timer> = None;
        unsafe {
            ONCE.call_once(|| {
                SINGLETON = Some(self::Timer::new());
            });
            SINGLETON.as_mut().unwrap()
        }
    }

    /// Converts a Duration to milliseconds.
    ///
    /// # Arguments
    ///
    /// * `duration` - The Duration to convert.
    ///
    /// # Returns
    ///
    /// Returns the converted milliseconds as a floating-point number.
    #[cfg(not(target_arch = "wasm32"))]
    fn duration_to_ms(duration: Duration) -> f64 {
        (duration.as_secs() as f64) * 1000.0 + (duration.subsec_nanos() as f64) / 1_000_000.0
    }
}

/// Implements the `Default` trait for `Timer`.
impl Default for Timer {
    /// Creates a default `Timer` instance.
    ///
    /// # Returns
    ///
    /// Returns a new `Timer` instance.
    fn default() -> Self {
        Self::new()
    }
}

/// Test module
#[cfg(test)]
mod tests {
    use super::*;
    #[cfg(not(target_arch = "wasm32"))]
    use std::thread::sleep;
    #[cfg(not(target_arch = "wasm32"))]
    use std::time::Duration;

    /// Tests Timer::new() and Timer::default()
    #[test]
    fn test_timer_new() {
        let timer = Timer::default();
        assert!(timer.timers.is_empty());
    }

    /// Tests Timer::time() method
    #[test]
    fn test_timer_time() {
        let mut timer = Timer::new();
        timer.time("test");
        assert!(timer.timers.contains_key("test"));
    }

    /// Tests Timer::time_log() method
    #[test]
    #[cfg(not(target_arch = "wasm32"))]
    fn test_timer_time_log() {
        let mut timer = Timer::new();
        timer.time("test_time_log");
        sleep(Duration::from_millis(10));
        let ms = timer.time_log("test_time_log", false);
        assert!(ms > 10.0 && ms < 15.0);
    }

    /// Tests Timer::time_end() method
    #[test]
    #[cfg(not(target_arch = "wasm32"))]
    fn test_timer_time_end() {
        let mut timer = Timer::new();
        timer.time("test_time_end");
        sleep(Duration::from_millis(10));
        timer.time_end("test_time_end", false);
        assert!(!timer.timers.contains_key("test"));
    }

    /// Tests Timer::duration_to_ms() method
    #[test]
    #[cfg(not(target_arch = "wasm32"))]
    fn test_duration_to_ms() {
        let duration = Duration::from_millis(1234);
        assert_eq!(Timer::duration_to_ms(duration), 1234.0);
    }
}