bento_kit/time/
time_use.rs1use std::collections::HashMap;
4use std::time::{Duration, Instant};
5
6#[derive(Debug, Clone, Copy)]
7struct Span {
8 start: Instant,
9 end: Option<Instant>,
10}
11
12#[derive(Debug)]
44pub struct TimeUse {
45 global_start: Instant,
46 global_end: Option<Instant>,
47 spans: HashMap<String, Span>,
48}
49
50impl TimeUse {
51 pub fn new() -> Self {
53 Self {
54 global_start: Instant::now(),
55 global_end: None,
56 spans: HashMap::new(),
57 }
58 }
59
60 pub fn start(&mut self, tag: Option<&str>) {
63 let now = Instant::now();
64 match tag {
65 None => {
66 self.global_start = now;
67 self.global_end = None;
68 }
69 Some(t) => {
70 self.spans.insert(
71 t.to_string(),
72 Span {
73 start: now,
74 end: None,
75 },
76 );
77 }
78 }
79 }
80
81 pub fn stop(&mut self, tag: Option<&str>) -> Duration {
88 let now = Instant::now();
89 match tag {
90 None => {
91 let end = *self.global_end.get_or_insert(now);
92 end.saturating_duration_since(self.global_start)
93 }
94 Some(t) => {
95 let global_start = self.global_start;
96 let span = self.spans.entry(t.to_string()).or_insert(Span {
97 start: global_start,
98 end: None,
99 });
100 let end = *span.end.get_or_insert(now);
101 end.saturating_duration_since(span.start)
102 }
103 }
104 }
105
106 pub fn elapsed(&self, tag: Option<&str>) -> Duration {
111 let now = Instant::now();
112 match tag {
113 None => {
114 let end = self.global_end.unwrap_or(now);
115 end.saturating_duration_since(self.global_start)
116 }
117 Some(t) => match self.spans.get(t) {
118 Some(span) => span
119 .end
120 .unwrap_or(now)
121 .saturating_duration_since(span.start),
122 None => now.saturating_duration_since(self.global_start),
123 },
124 }
125 }
126
127 pub fn restart(&mut self, tag: Option<&str>) -> Duration {
131 let elapsed = self.stop(tag);
132 self.start(tag);
133 elapsed
134 }
135
136 pub fn tag_count(&self) -> usize {
138 self.spans.len()
139 }
140}
141
142impl Default for TimeUse {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use std::thread::sleep;
152
153 #[test]
154 fn global_elapsed_grows() {
155 let t = TimeUse::new();
156 sleep(Duration::from_millis(5));
157 let a = t.elapsed(None);
158 sleep(Duration::from_millis(5));
159 let b = t.elapsed(None);
160 assert!(b >= a);
161 assert!(b.as_millis() >= 10);
162 }
163
164 #[test]
165 fn stop_is_idempotent() {
166 let mut t = TimeUse::new();
167 sleep(Duration::from_millis(10));
168 let first = t.stop(None);
169 sleep(Duration::from_millis(10));
170 let second = t.stop(None);
171 assert_eq!(first, second);
172 }
173
174 #[test]
175 fn elapsed_after_stop_returns_cached() {
176 let mut t = TimeUse::new();
177 sleep(Duration::from_millis(10));
178 let stopped = t.stop(None);
179 sleep(Duration::from_millis(10));
180 assert_eq!(t.elapsed(None), stopped);
181 }
182
183 #[test]
184 fn independent_tags() {
185 let mut t = TimeUse::new();
189 t.start(Some("a"));
190 sleep(Duration::from_millis(10));
191 let a = t.stop(Some("a"));
192
193 t.start(Some("b"));
194 sleep(Duration::from_millis(20));
195 let b = t.stop(Some("b"));
196
197 assert!(a.as_millis() >= 10);
198 assert!(b.as_millis() >= 20);
199 assert_eq!(t.tag_count(), 2);
200 }
201
202 #[test]
203 fn stop_tag_is_idempotent() {
204 let mut t = TimeUse::new();
205 t.start(Some("k"));
206 sleep(Duration::from_millis(5));
207 let first = t.stop(Some("k"));
208 sleep(Duration::from_millis(20));
209 let second = t.stop(Some("k"));
210 assert_eq!(first, second);
211 }
212
213 #[test]
214 fn stop_unknown_tag_falls_back_to_global_start() {
215 let mut t = TimeUse::new();
216 sleep(Duration::from_millis(10));
217 let elapsed = t.stop(Some("never_started"));
218 assert!(elapsed.as_millis() >= 10);
219 }
220
221 #[test]
222 fn elapsed_unknown_tag_falls_back_to_global_start() {
223 let t = TimeUse::new();
224 sleep(Duration::from_millis(10));
225 let elapsed = t.elapsed(Some("never_started"));
226 assert!(elapsed.as_millis() >= 10);
227 }
228
229 #[test]
230 fn stop_global_does_not_reset() {
231 let mut t = TimeUse::new();
232 sleep(Duration::from_millis(10));
233 let first = t.stop(None);
234 sleep(Duration::from_millis(20));
235 assert_eq!(t.stop(None), first);
236 }
237
238 #[test]
239 fn restart_global_returns_previous_duration() {
240 let mut t = TimeUse::new();
241 sleep(Duration::from_millis(10));
242 let prev = t.restart(None);
243 assert!(prev.as_millis() >= 10);
244 }
251
252 #[test]
253 fn restart_tag_returns_previous_duration() {
254 let mut t = TimeUse::new();
255 t.start(Some("loop"));
256 sleep(Duration::from_millis(10));
257 let first = t.restart(Some("loop"));
258 assert!(first.as_millis() >= 10);
259 }
262}