1use super::{TimerCompletion, TimerCompletionOutcome, TimerEpoch, TimerOutcomeSnapshot};
4
5#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
10pub struct TimerCounters {
11 schedule_requests: u64,
12 wakeups_armed: u64,
13 work_dispatched: u64,
14 scheduler_started: u64,
15 work_started: u64,
16 work_completed: u64,
17 succeeded: u64,
18 no_work: u64,
19 retryable_failure: u64,
20 invariant_failure: u64,
21 cancelled: u64,
22 stale_wakeups: u64,
23 stale_work: u64,
24 coalesced: u64,
25 unacknowledged: u64,
26}
27
28impl TimerCounters {
29 pub(crate) const fn record_schedule_request(&mut self) {
30 self.schedule_requests = self.schedule_requests.saturating_add(1);
31 }
32
33 pub(crate) const fn record_wakeup_armed(&mut self) {
34 self.wakeups_armed = self.wakeups_armed.saturating_add(1);
35 }
36
37 pub(crate) const fn record_work_dispatched(&mut self) {
38 self.work_dispatched = self.work_dispatched.saturating_add(1);
39 }
40
41 pub(crate) const fn record_scheduler_started(&mut self) {
42 self.scheduler_started = self.scheduler_started.saturating_add(1);
43 }
44
45 pub(crate) const fn record_work_started(&mut self) {
46 self.work_started = self.work_started.saturating_add(1);
47 }
48
49 pub(crate) const fn record_completion(&mut self, outcome: TimerCompletionOutcome) {
50 self.work_completed = self.work_completed.saturating_add(1);
51 match outcome {
52 TimerCompletionOutcome::Success => {
53 self.succeeded = self.succeeded.saturating_add(1);
54 }
55 TimerCompletionOutcome::NoWork => {
56 self.no_work = self.no_work.saturating_add(1);
57 }
58 TimerCompletionOutcome::RetryableFailure => {
59 self.retryable_failure = self.retryable_failure.saturating_add(1);
60 }
61 TimerCompletionOutcome::InvariantFailure => {
62 self.invariant_failure = self.invariant_failure.saturating_add(1);
63 }
64 }
65 }
66
67 pub(crate) const fn record_cancellation(&mut self) {
68 self.cancelled = self.cancelled.saturating_add(1);
69 }
70
71 pub(crate) const fn record_stale_wakeup(&mut self) {
72 self.stale_wakeups = self.stale_wakeups.saturating_add(1);
73 }
74
75 pub(crate) const fn record_stale_work(&mut self) {
76 self.stale_work = self.stale_work.saturating_add(1);
77 }
78
79 pub(crate) const fn record_coalesced(&mut self) {
80 self.coalesced = self.coalesced.saturating_add(1);
81 }
82
83 pub(crate) const fn record_unacknowledged(&mut self) {
84 self.unacknowledged = self.unacknowledged.saturating_add(1);
85 }
86
87 #[must_use]
89 pub const fn schedule_requests(self) -> u64 {
90 self.schedule_requests
91 }
92
93 #[must_use]
95 pub const fn wakeups_armed(self) -> u64 {
96 self.wakeups_armed
97 }
98
99 #[must_use]
101 pub const fn work_dispatched(self) -> u64 {
102 self.work_dispatched
103 }
104
105 #[must_use]
107 pub const fn provider_arms(self) -> u64 {
108 self.wakeups_armed.saturating_add(self.work_dispatched)
109 }
110
111 #[must_use]
113 pub const fn scheduler_started(self) -> u64 {
114 self.scheduler_started
115 }
116
117 #[must_use]
119 pub const fn work_started(self) -> u64 {
120 self.work_started
121 }
122
123 #[must_use]
125 pub const fn work_completed(self) -> u64 {
126 self.work_completed
127 }
128
129 #[must_use]
131 pub const fn succeeded(self) -> u64 {
132 self.succeeded
133 }
134
135 #[must_use]
137 pub const fn no_work(self) -> u64 {
138 self.no_work
139 }
140
141 #[must_use]
143 pub const fn retryable_failure(self) -> u64 {
144 self.retryable_failure
145 }
146
147 #[must_use]
149 pub const fn invariant_failure(self) -> u64 {
150 self.invariant_failure
151 }
152
153 #[must_use]
155 pub const fn cancelled(self) -> u64 {
156 self.cancelled
157 }
158
159 #[must_use]
161 pub const fn stale_wakeups(self) -> u64 {
162 self.stale_wakeups
163 }
164
165 #[must_use]
167 pub const fn stale_work(self) -> u64 {
168 self.stale_work
169 }
170
171 #[must_use]
173 pub const fn coalesced(self) -> u64 {
174 self.coalesced
175 }
176
177 #[must_use]
179 pub const fn unacknowledged(self) -> u64 {
180 self.unacknowledged
181 }
182
183 #[must_use]
185 pub const fn completion_partition_is_valid(self) -> bool {
186 self.work_completed
187 == self
188 .succeeded
189 .saturating_add(self.no_work)
190 .saturating_add(self.retryable_failure)
191 .saturating_add(self.invariant_failure)
192 }
193}
194
195#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
197pub struct MeasurementSummary {
198 samples: u64,
199 total: u64,
200 latest: Option<u64>,
201 maximum: Option<u64>,
202}
203
204impl MeasurementSummary {
205 pub(crate) const fn record(&mut self, value: u64) {
206 self.samples = self.samples.saturating_add(1);
207 self.total = self.total.saturating_add(value);
208 self.latest = Some(value);
209 self.maximum = Some(match self.maximum {
210 Some(current) if current > value => current,
211 Some(_) | None => value,
212 });
213 }
214
215 #[must_use]
217 pub const fn samples(self) -> u64 {
218 self.samples
219 }
220
221 #[must_use]
223 pub const fn total(self) -> u64 {
224 self.total
225 }
226
227 #[must_use]
229 pub const fn latest(self) -> Option<u64> {
230 self.latest
231 }
232
233 #[must_use]
235 pub const fn maximum(self) -> Option<u64> {
236 self.maximum
237 }
238}
239
240#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
242pub struct TimerPerformance {
243 scheduler_instructions: MeasurementSummary,
244 work_instructions: MeasurementSummary,
245}
246
247impl TimerPerformance {
248 pub(crate) const fn record_scheduler(&mut self, instructions: u64) {
249 self.scheduler_instructions.record(instructions);
250 }
251
252 pub(crate) const fn record_work(&mut self, instructions: u64) {
253 self.work_instructions.record(instructions);
254 }
255
256 #[must_use]
258 pub const fn scheduler_instructions(self) -> MeasurementSummary {
259 self.scheduler_instructions
260 }
261
262 #[must_use]
264 pub const fn work_instructions(self) -> MeasurementSummary {
265 self.work_instructions
266 }
267}
268
269#[derive(Clone, Copy, Debug, Eq, PartialEq)]
271pub struct TimerObservabilitySnapshot {
272 epoch: TimerEpoch,
273 outcomes: TimerOutcomeSnapshot,
274 counters: TimerCounters,
275 performance: TimerPerformance,
276}
277
278impl TimerObservabilitySnapshot {
279 pub(crate) const fn new(epoch: TimerEpoch) -> Self {
280 Self {
281 epoch,
282 outcomes: TimerOutcomeSnapshot::new(),
283 counters: TimerCounters {
284 schedule_requests: 0,
285 wakeups_armed: 0,
286 work_dispatched: 0,
287 scheduler_started: 0,
288 work_started: 0,
289 work_completed: 0,
290 succeeded: 0,
291 no_work: 0,
292 retryable_failure: 0,
293 invariant_failure: 0,
294 cancelled: 0,
295 stale_wakeups: 0,
296 stale_work: 0,
297 coalesced: 0,
298 unacknowledged: 0,
299 },
300 performance: TimerPerformance {
301 scheduler_instructions: MeasurementSummary {
302 samples: 0,
303 total: 0,
304 latest: None,
305 maximum: None,
306 },
307 work_instructions: MeasurementSummary {
308 samples: 0,
309 total: 0,
310 latest: None,
311 maximum: None,
312 },
313 },
314 }
315 }
316
317 pub(crate) const fn record_completion(
318 &mut self,
319 completion: TimerCompletion,
320 completed_at_ns: u64,
321 ) {
322 self.outcomes.record_completion(completion, completed_at_ns);
323 self.counters.record_completion(completion.outcome());
324 }
325
326 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
327 self.outcomes.record_unacknowledged(observed_at_ns);
328 self.counters.record_unacknowledged();
329 }
330
331 pub(crate) const fn counters_mut(&mut self) -> &mut TimerCounters {
332 &mut self.counters
333 }
334
335 pub(crate) const fn record_scheduler_instructions(&mut self, instructions: u64) {
336 self.performance.record_scheduler(instructions);
337 }
338
339 pub(crate) const fn record_work_instructions(&mut self, instructions: u64) {
340 self.performance.record_work(instructions);
341 }
342
343 #[must_use]
345 pub const fn epoch(self) -> TimerEpoch {
346 self.epoch
347 }
348
349 #[must_use]
351 pub const fn outcomes(self) -> TimerOutcomeSnapshot {
352 self.outcomes
353 }
354
355 #[must_use]
357 pub const fn counters(self) -> TimerCounters {
358 self.counters
359 }
360
361 #[must_use]
363 pub const fn performance(self) -> TimerPerformance {
364 self.performance
365 }
366
367 #[must_use]
369 pub const fn consecutive_expected_failures(self) -> u64 {
370 self.outcomes.consecutive_expected_failures()
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377
378 #[test]
379 fn all_counters_saturate() {
380 let mut counters = TimerCounters {
381 schedule_requests: u64::MAX,
382 wakeups_armed: u64::MAX,
383 work_dispatched: u64::MAX,
384 scheduler_started: u64::MAX,
385 work_started: u64::MAX,
386 work_completed: u64::MAX,
387 succeeded: u64::MAX,
388 no_work: u64::MAX,
389 retryable_failure: u64::MAX,
390 invariant_failure: u64::MAX,
391 cancelled: u64::MAX,
392 stale_wakeups: u64::MAX,
393 stale_work: u64::MAX,
394 coalesced: u64::MAX,
395 unacknowledged: u64::MAX,
396 };
397
398 counters.record_schedule_request();
399 counters.record_wakeup_armed();
400 counters.record_work_dispatched();
401 counters.record_scheduler_started();
402 counters.record_work_started();
403 counters.record_completion(TimerCompletionOutcome::Success);
404 counters.record_cancellation();
405 counters.record_stale_wakeup();
406 counters.record_stale_work();
407 counters.record_coalesced();
408 counters.record_unacknowledged();
409
410 assert_eq!(counters.schedule_requests(), u64::MAX);
411 assert_eq!(counters.provider_arms(), u64::MAX);
412 assert_eq!(counters.work_completed(), u64::MAX);
413 assert_eq!(counters.cancelled(), u64::MAX);
414 assert_eq!(counters.stale_wakeups(), u64::MAX);
415 assert_eq!(counters.stale_work(), u64::MAX);
416 assert_eq!(counters.coalesced(), u64::MAX);
417 assert_eq!(counters.unacknowledged(), u64::MAX);
418 assert!(counters.completion_partition_is_valid());
419 }
420
421 #[test]
422 fn instruction_roles_are_separate_and_saturating() {
423 let mut performance = TimerPerformance::default();
424 performance.record_scheduler(20);
425 performance.record_work(30);
426 performance.record_work(10);
427
428 assert_eq!(performance.scheduler_instructions().total(), 20);
429 assert_eq!(performance.work_instructions().samples(), 2);
430 assert_eq!(performance.work_instructions().total(), 40);
431 assert_eq!(performance.work_instructions().latest(), Some(10));
432 assert_eq!(performance.work_instructions().maximum(), Some(30));
433 }
434}