streaming_crypto/core_api/telemetry/timers.rs
1// ## src/telemetry/timers.rs
2
3//! telemetry/timers.rs
4//! Stage timers for streaming pipelines.
5//!
6//! Summary: Records durations for read, compress, encrypt/decrypt, and write stages.
7//! Industry notes: TLS/QUIC libraries track per-record timings for performance analysis.
8
9// ### `Stage` enum with `Display`
10
11use std::fmt;
12use std::time::Instant;
13use std::time::Duration;
14use std::collections::{HashMap, hash_map};
15use serde::{Serialize, Deserialize};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub enum Stage {
19 Read,
20 Write,
21 Encode,
22 Decode,
23 Compress,
24 Decompress,
25 Encrypt,
26 Decrypt,
27 Validate,
28 Digest,
29}
30
31impl fmt::Display for Stage {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 let name = match self {
34 Stage::Read => "read",
35 Stage::Write => "write",
36 Stage::Encode => "encode",
37 Stage::Decode => "decode",
38 Stage::Compress => "compress",
39 Stage::Decompress => "decompress",
40 Stage::Encrypt => "encrypt",
41 Stage::Decrypt => "decrypt",
42 Stage::Validate => "validate",
43 Stage::Digest => "digest",
44 };
45 f.write_str(name)
46 }
47}
48
49// ### Benefits
50// - **Type safety**: we call `Stage::Encrypt` instead of `"encrypt"`.
51// - **Human‑readable output**: `format!("{}", Stage::Encrypt)` → `"encrypt"`.
52// - **FFI/logging friendly**: we can pass stage names to telemetry snapshots or JSON without stringly‑typed code.
53// - **Extensible**: add new stages by extending the enum and updating the `Display` match.
54
55// - **Microsecond precision**: expose both microseconds and milliseconds helpers, but keep `Duration` internally (nanosecond precision).
56// - **Type‑safe iteration**: implement `IntoIterator` so we can loop over `(Stage, Duration)` pairs directly.
57// - **Cleaner API**: replace `get_ms` with `get_ms` and `get_us`, and add a generic `get_in_unit` if we want flexibility.
58// - **Extensibility**: we can add new stages without changing the struct internals.
59
60// ### ⚠️ Note on `Duration`
61// `std::time::Duration` already implements `Serialize` and `Deserialize` (via the `serde` crate), so we don’t need to do anything extra there.
62
63#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
64pub struct StageTimes {
65 pub times: HashMap<Stage, Duration>,
66}
67// ### 🚀 Summary
68// - Add `#[derive(Serialize, Deserialize)]` to `Stage`.
69// - Keep `StageTimes` deriving `Serialize, Deserialize`.
70// - Now `TelemetrySnapshot` can derive `Serialize, Deserialize` without errors.
71
72impl StageTimes {
73 /// Add duration to a stage (accumulates if already present).
74 // pub fn add(&mut self, stage: Stage, dur: Duration) {
75 // *self.times.entry(stage).or_insert(Duration::ZERO) += dur;
76 // }
77 pub fn add(&mut self, stage: Stage, dur: Duration) {
78 self.times.insert(stage, dur);
79 }
80
81
82 /// Get total duration for a stage.
83 pub fn get(&self, stage: Stage) -> Duration {
84 self.times.get(&stage).copied().unwrap_or(Duration::ZERO)
85 }
86
87 /// Get duration in milliseconds (f64).
88 pub fn get_ms(&self, stage: Stage) -> f64 {
89 self.get(stage).as_secs_f64() * 1_000.0
90 }
91
92 /// Get duration in microseconds (f64).
93 pub fn get_us(&self, stage: Stage) -> f64 {
94 self.get(stage).as_secs_f64() * 1_000_000.0
95 }
96
97 /// Get duration in nanoseconds (u128).
98 pub fn get_ns(&self, stage: Stage) -> u128 {
99 self.get(stage).as_nanos()
100 }
101
102 /// Sum all stage durations.
103 pub fn total(&self) -> Duration {
104 self.times.values().copied().sum()
105 }
106
107 /// Return all stage times.
108 pub fn all(&self) -> &HashMap<Stage, Duration> {
109 &self.times
110 }
111
112 /// Check if all expected stages are present (non-zero).
113 pub fn has_all(&self, expected: &[Stage]) -> bool {
114 expected.iter().all(|s| self.get(*s) > Duration::ZERO)
115 }
116
117 // ### Example usage
118
119 // ```rust
120 // let mut timer = TelemetryTimer::new();
121 // timer.add_stage_time(Stage::Encrypt, Duration::from_micros(420));
122 // timer.add_stage_time(Stage::Read, Duration::from_micros(100));
123
124 // println!("Total stage time: {} µs", timer.stage_times.total().as_micros());
125
126 // let expected = [Stage::Read, Stage::Encrypt];
127 // assert!(timer.stage_times.has_all(&expected));
128 // ```
129
130 // Output:
131
132 // ```
133 // Total stage time: 520 µs
134 // ```
135
136 // ### ✅ Benefits
137 // - **Telemetry sanity**: compare `StageTimes::total()` against `TelemetrySnapshot.elapsed` to ensure no stage time exceeds total elapsed.
138 // - **Coverage check**: `has_all()` lets us assert that all critical stages were measured.
139 // - **Precision**: we can report totals in microseconds or nanoseconds without losing detail.
140
141 pub fn iter(&self) -> impl Iterator<Item = (&Stage, &Duration)> {
142 self.times.iter()
143 }
144
145 /// Merge another StageTimes into this one
146 pub fn merge(&mut self, other: &StageTimes) {
147 for (stage, dur) in &other.times {
148 *self.times.entry(*stage).or_insert(Duration::ZERO) += *dur;
149 }
150 }
151
152 pub fn summary(&self) -> String {
153 let mut out = String::new();
154 out.push_str("=== Stage Times Summary ===\n");
155 for (stage, dur) in &self.times {
156 out.push_str(&format!("{:?}: {:?}\n", stage, dur));
157 }
158 out
159 }
160
161 // println!("{}", st.summary());
162 // or, with Display:
163 // println!("{}", st);
164
165}
166
167// Optional: implement Display so we can just `println!("{}", stage_times)`
168impl fmt::Display for StageTimes {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 writeln!(f, "=== Stage Times Summary ===")?;
171 for (stage, dur) in &self.times {
172 writeln!(f, "{:?}: {:?}", stage, dur)?;
173 }
174 Ok(())
175 }
176}
177
178/// Allow iteration over owned StageTimes.
179impl IntoIterator for StageTimes {
180 type Item = (Stage, Duration);
181 type IntoIter = hash_map::IntoIter<Stage, Duration>;
182
183 fn into_iter(self) -> Self::IntoIter {
184 self.times.into_iter()
185 }
186}
187
188/// Allow iteration over borrowed StageTimes.
189impl<'a> IntoIterator for &'a StageTimes {
190 type Item = (&'a Stage, &'a Duration);
191 type IntoIter = hash_map::Iter<'a, Stage, Duration>;
192
193 fn into_iter(self) -> Self::IntoIter {
194 self.times.iter()
195 }
196}
197
198// ### ✅ Benefits
199// - **Precision**: we can now report in microseconds or nanoseconds, not just milliseconds.
200// - **Flexibility**: `get_ms`, `get_us`, and `get_ns` cover most telemetry needs.
201// - **Iteration**: we can loop over all stages easily in tests or telemetry snapshots.
202// - **Extensibility**: adding new stages only requires updating the `Stage` enum, not this struct.
203
204// Example usage:
205
206// ```rust
207// timer.add_stage_time(Stage::Encrypt, t.elapsed());
208// println!("Encrypt stage took {:.2} µs", timer.stage_times.get_us(Stage::Encrypt));
209
210// for (stage, dur) in &timer.stage_times {
211// println!("Stage {} took {} ns", stage, dur.as_nanos());
212// }
213// ```
214
215// ### ✅ Benefits
216// - Clean iteration without exposing `HashMap` internals.
217// - Works for both owned (`StageTimes`) and borrowed (`&StageTimes`) contexts.
218// - Plays nicely with our `Display` impl for `Stage`.
219
220
221// ### ✅ TelemetryTimer with Enum
222
223// Now `TelemetryTimer` can use the enum directly:
224
225#[derive(Clone, Debug)]
226pub struct TelemetryTimer {
227 pub start_time: Instant,
228 pub end_time: Option<Instant>,
229 pub stage_times: StageTimes,
230}
231
232impl TelemetryTimer {
233 pub fn new() -> Self {
234 Self {
235 start_time: Instant::now(),
236 end_time: None,
237 stage_times: StageTimes::default(),
238 }
239 }
240
241 pub fn finish(&mut self) {
242 self.end_time = Some(Instant::now());
243 }
244
245 pub fn add_stage_time(&mut self, stage: Stage, dur: Duration) {
246 self.stage_times.add(stage, dur);
247 }
248
249 pub fn elapsed(&self) -> Duration {
250 match self.end_time {
251 Some(end) => end.duration_since(self.start_time),
252 None => Instant::now().duration_since(self.start_time),
253 }
254 }
255
256 /// Merge another StageTimes into the global timer
257 pub fn merge(&mut self, other: &StageTimes) {
258 self.stage_times.merge(other);
259 }
260
261}
262
263// Usage becomes type‑safe:
264
265// ```rust
266// timer.add_stage_time(Stage::Encrypt, res.encrypt);
267// timer.add_stage_time(Stage::Read, t.elapsed());
268// ```
269
270// ### 🚀 Improvements over current design
271// - **Type safety**: no silent typos in stage names.
272// - **Extensibility**: add new stages by extending the `Stage` enum, no need to touch `StageTimes` internals.
273// - **Cleaner API**: `get_ms(stage)` instead of separate `read_ms()`, `compress_ms()`, etc.
274// - **Telemetry snapshot sanity**: we can iterate over `stage_times.all()` to check that all expected stages are present.