fastmcp_rust/testing/
timing.rs1use std::time::{Duration, Instant};
7
8pub fn measure_duration<T, F: FnOnce() -> T>(f: F) -> (T, Duration) {
24 let start = Instant::now();
25 let result = f();
26 let duration = start.elapsed();
27 (result, duration)
28}
29
30#[derive(Debug, Clone)]
51pub struct Stopwatch {
52 start: Instant,
54 last_lap: Instant,
56 laps: Vec<(String, Duration)>,
58}
59
60impl Default for Stopwatch {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl Stopwatch {
67 #[must_use]
69 pub fn new() -> Self {
70 let now = Instant::now();
71 Self {
72 start: now,
73 last_lap: now,
74 laps: Vec::new(),
75 }
76 }
77
78 #[must_use]
80 pub fn stopped() -> Self {
81 let now = Instant::now();
83 Self {
84 start: now,
85 last_lap: now,
86 laps: Vec::new(),
87 }
88 }
89
90 pub fn restart(&mut self) {
92 let now = Instant::now();
93 self.start = now;
94 self.last_lap = now;
95 self.laps.clear();
96 }
97
98 pub fn lap(&mut self, name: impl Into<String>) {
102 let now = Instant::now();
103 let duration = now - self.last_lap;
104 self.laps.push((name.into(), duration));
105 self.last_lap = now;
106 }
107
108 #[must_use]
110 pub fn elapsed(&self) -> Duration {
111 self.start.elapsed()
112 }
113
114 #[must_use]
116 pub fn since_last_lap(&self) -> Duration {
117 self.last_lap.elapsed()
118 }
119
120 #[must_use]
122 pub fn laps(&self) -> &[(String, Duration)] {
123 &self.laps
124 }
125
126 #[must_use]
128 pub fn lap_count(&self) -> usize {
129 self.laps.len()
130 }
131
132 #[must_use]
134 pub fn get_lap(&self, name: &str) -> Option<Duration> {
135 self.laps.iter().find(|(n, _)| n == name).map(|(_, d)| *d)
136 }
137
138 #[must_use]
140 pub fn total_lap_time(&self) -> Duration {
141 self.laps.iter().map(|(_, d)| *d).sum()
142 }
143
144 #[must_use]
146 pub fn stats(&self) -> TimingStats {
147 if self.laps.is_empty() {
148 return TimingStats {
149 count: 0,
150 total: Duration::ZERO,
151 min: None,
152 max: None,
153 mean: None,
154 };
155 }
156
157 let durations: Vec<_> = self.laps.iter().map(|(_, d)| *d).collect();
158 let total: Duration = durations.iter().sum();
159 let min = durations.iter().min().copied();
160 let max = durations.iter().max().copied();
161 let mean = Some(total / durations.len() as u32);
162
163 TimingStats {
164 count: durations.len(),
165 total,
166 min,
167 max,
168 mean,
169 }
170 }
171
172 #[must_use]
174 pub fn report(&self) -> String {
175 let mut lines = Vec::new();
176 lines.push(format!("Total elapsed: {:?}", self.elapsed()));
177 lines.push(format!("Laps: {}", self.lap_count()));
178
179 if !self.laps.is_empty() {
180 lines.push(String::new());
181 for (name, duration) in &self.laps {
182 lines.push(format!(" {name}: {duration:?}"));
183 }
184
185 let stats = self.stats();
186 lines.push(String::new());
187 lines.push("Statistics:".to_string());
188 if let Some(min) = stats.min {
189 lines.push(format!(" Min: {min:?}"));
190 }
191 if let Some(max) = stats.max {
192 lines.push(format!(" Max: {max:?}"));
193 }
194 if let Some(mean) = stats.mean {
195 lines.push(format!(" Mean: {mean:?}"));
196 }
197 }
198
199 lines.join("\n")
200 }
201}
202
203#[derive(Debug, Clone)]
205pub struct TimingStats {
206 pub count: usize,
208 pub total: Duration,
210 pub min: Option<Duration>,
212 pub max: Option<Duration>,
214 pub mean: Option<Duration>,
216}
217
218impl TimingStats {
219 #[must_use]
221 pub fn is_empty(&self) -> bool {
222 self.count == 0
223 }
224}
225
226#[derive(Debug, Clone)]
230pub struct Timer {
231 start: Instant,
233 duration: Duration,
235}
236
237impl Timer {
238 #[must_use]
240 pub fn new(duration: Duration) -> Self {
241 Self {
242 start: Instant::now(),
243 duration,
244 }
245 }
246
247 #[must_use]
249 pub fn from_secs(secs: u64) -> Self {
250 Self::new(Duration::from_secs(secs))
251 }
252
253 #[must_use]
255 pub fn from_millis(ms: u64) -> Self {
256 Self::new(Duration::from_millis(ms))
257 }
258
259 #[must_use]
261 pub fn is_expired(&self) -> bool {
262 self.start.elapsed() >= self.duration
263 }
264
265 #[must_use]
267 pub fn remaining(&self) -> Duration {
268 let elapsed = self.start.elapsed();
269 if elapsed >= self.duration {
270 Duration::ZERO
271 } else {
272 self.duration - elapsed
273 }
274 }
275
276 pub fn reset(&mut self) {
278 self.start = Instant::now();
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285 use std::thread::sleep;
286
287 #[test]
288 fn test_measure_duration() {
289 let (result, duration) = measure_duration(|| {
290 sleep(Duration::from_millis(10));
291 42
292 });
293
294 assert_eq!(result, 42);
295 assert!(duration >= Duration::from_millis(10));
296 }
297
298 #[test]
299 fn test_stopwatch_basic() {
300 let mut sw = Stopwatch::new();
301 sleep(Duration::from_millis(10));
302 sw.lap("first");
303 sleep(Duration::from_millis(10));
304 sw.lap("second");
305
306 assert_eq!(sw.lap_count(), 2);
307 assert!(sw.elapsed() >= Duration::from_millis(20));
308 }
309
310 #[test]
311 fn test_stopwatch_get_lap() {
312 let mut sw = Stopwatch::new();
313 sleep(Duration::from_millis(5));
314 sw.lap("test_lap");
315
316 let lap = sw.get_lap("test_lap");
317 assert!(lap.is_some());
318 assert!(lap.unwrap() >= Duration::from_millis(5));
319
320 assert!(sw.get_lap("nonexistent").is_none());
321 }
322
323 #[test]
324 fn test_stopwatch_stats() {
325 let mut sw = Stopwatch::new();
326 sw.lap("a");
327 sw.lap("b");
328 sw.lap("c");
329
330 let stats = sw.stats();
331 assert_eq!(stats.count, 3);
332 assert!(stats.min.is_some());
333 assert!(stats.max.is_some());
334 assert!(stats.mean.is_some());
335 }
336
337 #[test]
338 fn test_stopwatch_empty_stats() {
339 let sw = Stopwatch::new();
340 let stats = sw.stats();
341 assert_eq!(stats.count, 0);
342 assert!(stats.is_empty());
343 }
344
345 #[test]
346 fn test_stopwatch_restart() {
347 let mut sw = Stopwatch::new();
348 sw.lap("first");
349 sw.restart();
350
351 assert_eq!(sw.lap_count(), 0);
352 }
353
354 #[test]
355 fn test_timer_basic() {
356 let timer = Timer::from_millis(50);
357 assert!(!timer.is_expired());
358 assert!(timer.remaining() > Duration::ZERO);
359
360 sleep(Duration::from_millis(60));
361 assert!(timer.is_expired());
362 assert_eq!(timer.remaining(), Duration::ZERO);
363 }
364
365 #[test]
366 fn test_timer_reset() {
367 let mut timer = Timer::from_millis(100);
368 sleep(Duration::from_millis(50));
369 timer.reset();
370
371 assert!(timer.remaining() > Duration::from_millis(80));
373 }
374
375 #[test]
376 fn test_stopwatch_report() {
377 let mut sw = Stopwatch::new();
378 sw.lap("setup");
379 sw.lap("execute");
380 sw.lap("teardown");
381
382 let report = sw.report();
383 assert!(report.contains("Total elapsed"));
384 assert!(report.contains("Laps: 3"));
385 assert!(report.contains("setup"));
386 assert!(report.contains("execute"));
387 assert!(report.contains("teardown"));
388 }
389
390 #[test]
395 fn stopwatch_stopped_constructor() {
396 let sw = Stopwatch::stopped();
397 assert_eq!(sw.lap_count(), 0);
398 assert!(sw.laps().is_empty());
399 }
400
401 #[test]
402 fn stopwatch_default_matches_new() {
403 let def = Stopwatch::default();
404 let new = Stopwatch::new();
405 assert_eq!(def.lap_count(), new.lap_count());
406 }
407
408 #[test]
409 fn stopwatch_since_last_lap() {
410 let sw = Stopwatch::new();
411 let since = sw.since_last_lap();
412 assert!(since < Duration::from_secs(1));
413 }
414
415 #[test]
416 fn stopwatch_total_lap_time() {
417 let mut sw = Stopwatch::new();
418 sw.lap("a");
419 sw.lap("b");
420 let total = sw.total_lap_time();
421 assert!(total > Duration::ZERO);
422 }
423
424 #[test]
425 fn stopwatch_debug_and_clone() {
426 let mut sw = Stopwatch::new();
427 sw.lap("x");
428 let debug = format!("{sw:?}");
429 assert!(debug.contains("Stopwatch"));
430
431 let cloned = sw.clone();
432 assert_eq!(cloned.lap_count(), 1);
433 }
434
435 #[test]
436 fn timing_stats_debug_clone_and_is_empty() {
437 let mut sw = Stopwatch::new();
438 sw.lap("a");
439 let stats = sw.stats();
440 let debug = format!("{stats:?}");
441 assert!(debug.contains("TimingStats"));
442
443 let cloned = stats.clone();
444 assert_eq!(cloned.count, 1);
445 assert!(!cloned.is_empty());
446 }
447
448 #[test]
449 fn timer_from_secs() {
450 let timer = Timer::from_secs(60);
451 assert!(!timer.is_expired());
452 assert!(timer.remaining() > Duration::from_secs(59));
453 }
454
455 #[test]
456 fn timer_debug_and_clone() {
457 let timer = Timer::from_millis(100);
458 let debug = format!("{timer:?}");
459 assert!(debug.contains("Timer"));
460
461 let cloned = timer.clone();
462 assert!(!cloned.is_expired());
463 }
464
465 #[test]
466 fn report_empty_laps() {
467 let sw = Stopwatch::new();
468 let report = sw.report();
469 assert!(report.contains("Laps: 0"));
470 assert!(!report.contains("Statistics"));
471 }
472}