1use std::cell::Cell;
24use std::time::Instant;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32#[non_exhaustive]
33pub enum Stage {
34 Read,
36 Decompress,
38 Decode,
40 Dictionary,
42 Assemble,
44}
45
46const STAGES: usize = 5;
48
49impl Stage {
50 pub const ALL: [Self; STAGES] =
52 [Self::Read, Self::Decompress, Self::Decode, Self::Dictionary, Self::Assemble];
53
54 #[must_use]
56 pub const fn name(self) -> &'static str {
57 match self {
58 Self::Read => "read",
59 Self::Decompress => "decompress",
60 Self::Decode => "decode",
61 Self::Dictionary => "dictionary",
62 Self::Assemble => "assemble",
63 }
64 }
65
66 #[must_use]
68 pub const fn slot(self) -> usize {
69 match self {
70 Self::Read => 0,
71 Self::Decompress => 1,
72 Self::Decode => 2,
73 Self::Dictionary => 3,
74 Self::Assemble => 4,
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
86pub struct Spent {
87 nanos: [u64; STAGES],
88 bytes: [u64; STAGES],
89}
90
91impl Spent {
92 #[must_use]
94 pub const fn none() -> Self {
95 Self { nanos: [0; STAGES], bytes: [0; STAGES] }
96 }
97
98 #[must_use]
100 pub const fn nanos(&self, stage: Stage) -> u64 {
101 self.nanos[stage.slot()]
102 }
103
104 #[must_use]
106 pub const fn bytes(&self, stage: Stage) -> u64 {
107 self.bytes[stage.slot()]
108 }
109
110 #[must_use]
112 pub fn total(&self) -> u64 {
113 self.nanos.iter().fold(0, |sum, nanos| sum.saturating_add(*nanos))
114 }
115
116 #[must_use]
118 pub fn is_empty(&self) -> bool {
119 self.nanos.iter().all(|nanos| *nanos == 0) && self.bytes.iter().all(|bytes| *bytes == 0)
120 }
121
122 pub fn taken(&self) -> impl Iterator<Item = (Stage, u64, u64)> + '_ {
128 Stage::ALL
129 .into_iter()
130 .map(|stage| (stage, self.nanos(stage), self.bytes(stage)))
131 .filter(|(_, nanos, bytes)| *nanos > 0 || *bytes > 0)
132 }
133
134 #[must_use]
136 pub fn worst(&self) -> Option<(Stage, u64)> {
137 self.taken()
138 .map(|(stage, nanos, _)| (stage, nanos))
139 .filter(|(_, nanos)| *nanos > 0)
140 .max_by_key(|(stage, nanos)| (*nanos, std::cmp::Reverse(stage.slot())))
141 }
142
143 #[must_use]
148 pub fn since(&self, before: Self) -> Self {
149 let mut out = Self::none();
150 for slot in 0..STAGES {
151 out.nanos[slot] = self.nanos[slot].saturating_sub(before.nanos[slot]);
152 out.bytes[slot] = self.bytes[slot].saturating_sub(before.bytes[slot]);
153 }
154 out
155 }
156
157 pub fn add(&mut self, other: Self) {
159 for slot in 0..STAGES {
160 self.nanos[slot] = self.nanos[slot].saturating_add(other.nanos[slot]);
161 self.bytes[slot] = self.bytes[slot].saturating_add(other.bytes[slot]);
162 }
163 }
164
165 #[must_use]
167 pub fn of(stage: Stage, nanos: u64, bytes: u64) -> Self {
168 let mut spent = Self::none();
169 spent.nanos[stage.slot()] = nanos;
170 spent.bytes[stage.slot()] = bytes;
171 spent
172 }
173}
174
175thread_local! {
176 static SPENT: Cell<Spent> = const { Cell::new(Spent::none()) };
178}
179
180pub fn took(stage: Stage, nanos: u64, bytes: u64) {
182 SPENT.with(|spent| {
183 let mut now = spent.get();
184 now.add(Spent::of(stage, nanos, bytes));
185 spent.set(now);
186 });
187}
188
189#[must_use]
191pub fn here() -> Spent {
192 SPENT.with(Cell::get)
193}
194
195pub fn reset() {
200 SPENT.with(|spent| spent.set(Spent::none()));
201}
202
203#[derive(Debug)]
208pub struct Timing {
209 stage: Stage,
210 at: Instant,
211}
212
213impl Timing {
214 #[must_use]
216 pub fn start(stage: Stage) -> Self {
217 Self { stage, at: Instant::now() }
218 }
219
220 pub fn stop(self, bytes: u64) {
225 let nanos = u64::try_from(self.at.elapsed().as_nanos()).unwrap_or(u64::MAX);
226 took(self.stage, nanos, bytes);
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::{Spent, Stage, here, reset, took};
233
234 #[test]
235 fn time_lands_against_its_own_stage_and_leaves_the_rest_alone() {
236 reset();
237 took(Stage::Read, 100, 4096);
238 took(Stage::Read, 50, 1024);
239 took(Stage::Decompress, 700, 8192);
240 let spent = here();
241 assert_eq!(spent.nanos(Stage::Read), 150);
242 assert_eq!(spent.bytes(Stage::Read), 5120);
243 assert_eq!(spent.nanos(Stage::Decompress), 700);
244 assert_eq!(spent.nanos(Stage::Decode), 0);
245 assert_eq!(spent.total(), 850);
246 reset();
247 }
248
249 #[test]
250 fn a_difference_is_what_happened_between_the_two_readings_and_nothing_before_them() {
251 reset();
252 took(Stage::Decode, 900, 16);
253 let before = here();
254 took(Stage::Assemble, 12, 0);
255 let during = here().since(before);
256 assert_eq!(during.nanos(Stage::Assemble), 12);
257 assert_eq!(during.nanos(Stage::Decode), 0, "what happened before the reading is not in it");
258 assert_eq!(during.total(), 12);
259 reset();
260 }
261
262 #[test]
263 fn a_difference_taken_backwards_reports_nothing_rather_than_most_of_a_century() {
264 let later = Spent::of(Stage::Read, 900, 900);
265 assert!(Spent::none().since(later).is_empty());
266 }
267
268 #[test]
269 fn the_worst_stage_is_the_one_worth_working_on() {
270 let mut spent = Spent::of(Stage::Read, 40, 0);
271 spent.add(Spent::of(Stage::Decompress, 4000, 0));
272 spent.add(Spent::of(Stage::Decode, 900, 0));
273 assert_eq!(spent.worst(), Some((Stage::Decompress, 4000)));
274 assert_eq!(spent.taken().count(), 3);
275 assert_eq!(Spent::none().worst(), None);
276 }
277
278 #[test]
279 fn a_stage_that_only_moved_bytes_is_listed_and_is_not_the_worst() {
280 let mut spent = Spent::of(Stage::Read, 0, 8192);
281 spent.add(Spent::of(Stage::Decode, 5, 0));
282 let listed: Vec<&str> = spent.taken().map(|(stage, _, _)| stage.name()).collect();
283 assert_eq!(listed, ["read", "decode"]);
284 assert_eq!(spent.worst(), Some((Stage::Decode, 5)));
285 }
286
287 #[test]
288 fn one_thread_timing_is_invisible_to_another() {
289 reset();
290 took(Stage::Dictionary, 44, 0);
291 let elsewhere = std::thread::spawn(|| {
292 took(Stage::Dictionary, 1, 0);
293 here()
294 })
295 .join()
296 .expect("no timing thread panics");
297 assert_eq!(elsewhere.nanos(Stage::Dictionary), 1, "the other thread starts from nothing");
298 assert_eq!(here().nanos(Stage::Dictionary), 44, "and does not add to this one");
299 reset();
300 }
301
302 #[test]
303 fn every_stage_has_its_own_slot_and_its_own_name() {
304 let mut seen: Vec<&str> = Stage::ALL.iter().map(|stage| stage.name()).collect();
305 seen.sort_unstable();
306 seen.dedup();
307 assert_eq!(seen.len(), Stage::ALL.len());
308 for stage in Stage::ALL {
309 assert_eq!(Spent::of(stage, 7, 3).total(), 7);
310 assert_eq!(Spent::of(stage, 7, 3).bytes(stage), 3);
311 }
312 }
313}