1use super::{TimerCompletion, TimerCompletionOutcome, TimerEpoch, TimerOutcomeSnapshot};
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub struct TimerCounters {
14 schedule_requests: u64,
15 wakeups_armed: u64,
16 work_dispatched: u64,
17 scheduler_started: u64,
18 work_started: u64,
19 succeeded: u64,
20 no_work: u64,
21 retryable_failure: u64,
22 invariant_failure: u64,
23 cancelled: u64,
24 stale_wakeups: u64,
25 stale_work: u64,
26 coalesced: u64,
27 unacknowledged: u64,
28}
29
30impl TimerCounters {
31 const EMPTY: Self = Self {
32 schedule_requests: 0,
33 wakeups_armed: 0,
34 work_dispatched: 0,
35 scheduler_started: 0,
36 work_started: 0,
37 succeeded: 0,
38 no_work: 0,
39 retryable_failure: 0,
40 invariant_failure: 0,
41 cancelled: 0,
42 stale_wakeups: 0,
43 stale_work: 0,
44 coalesced: 0,
45 unacknowledged: 0,
46 };
47
48 pub(crate) const fn record_schedule_request(&mut self) {
49 self.schedule_requests = self.schedule_requests.saturating_add(1);
50 }
51
52 pub(crate) const fn record_wakeup_armed(&mut self) {
53 self.wakeups_armed = self.wakeups_armed.saturating_add(1);
54 }
55
56 pub(crate) const fn record_work_dispatched(&mut self) {
57 self.work_dispatched = self.work_dispatched.saturating_add(1);
58 }
59
60 pub(crate) const fn record_scheduler_started(&mut self) {
61 self.scheduler_started = self.scheduler_started.saturating_add(1);
62 }
63
64 pub(crate) const fn record_work_started(&mut self) {
65 self.work_started = self.work_started.saturating_add(1);
66 }
67
68 pub(crate) const fn record_completion(&mut self, outcome: TimerCompletionOutcome) {
69 match outcome {
70 TimerCompletionOutcome::Success => {
71 self.succeeded = self.succeeded.saturating_add(1);
72 }
73 TimerCompletionOutcome::NoWork => {
74 self.no_work = self.no_work.saturating_add(1);
75 }
76 TimerCompletionOutcome::RetryableFailure => {
77 self.retryable_failure = self.retryable_failure.saturating_add(1);
78 }
79 TimerCompletionOutcome::InvariantFailure => {
80 self.invariant_failure = self.invariant_failure.saturating_add(1);
81 }
82 }
83 }
84
85 pub(crate) const fn record_cancellation(&mut self) {
86 self.cancelled = self.cancelled.saturating_add(1);
87 }
88
89 pub(crate) const fn record_stale_wakeup(&mut self) {
90 self.stale_wakeups = self.stale_wakeups.saturating_add(1);
91 }
92
93 pub(crate) const fn record_stale_work(&mut self) {
94 self.stale_work = self.stale_work.saturating_add(1);
95 }
96
97 pub(crate) const fn record_coalesced(&mut self) {
98 self.coalesced = self.coalesced.saturating_add(1);
99 }
100
101 pub(crate) const fn record_unacknowledged(&mut self) {
102 self.unacknowledged = self.unacknowledged.saturating_add(1);
103 }
104
105 #[must_use]
107 pub const fn schedule_requests(self) -> u64 {
108 self.schedule_requests
109 }
110
111 #[must_use]
113 pub const fn wakeups_armed(self) -> u64 {
114 self.wakeups_armed
115 }
116
117 #[must_use]
119 pub const fn work_dispatched(self) -> u64 {
120 self.work_dispatched
121 }
122
123 #[must_use]
125 pub const fn scheduler_started(self) -> u64 {
126 self.scheduler_started
127 }
128
129 #[must_use]
131 pub const fn work_started(self) -> u64 {
132 self.work_started
133 }
134
135 #[must_use]
139 pub const fn work_completed(self) -> u64 {
140 self.succeeded
141 .saturating_add(self.no_work)
142 .saturating_add(self.retryable_failure)
143 .saturating_add(self.invariant_failure)
144 }
145
146 #[must_use]
148 pub const fn succeeded(self) -> u64 {
149 self.succeeded
150 }
151
152 #[must_use]
154 pub const fn no_work(self) -> u64 {
155 self.no_work
156 }
157
158 #[must_use]
160 pub const fn retryable_failure(self) -> u64 {
161 self.retryable_failure
162 }
163
164 #[must_use]
166 pub const fn invariant_failure(self) -> u64 {
167 self.invariant_failure
168 }
169
170 #[must_use]
172 pub const fn cancelled(self) -> u64 {
173 self.cancelled
174 }
175
176 #[must_use]
178 pub const fn stale_wakeups(self) -> u64 {
179 self.stale_wakeups
180 }
181
182 #[must_use]
184 pub const fn stale_work(self) -> u64 {
185 self.stale_work
186 }
187
188 #[must_use]
190 pub const fn coalesced(self) -> u64 {
191 self.coalesced
192 }
193
194 #[must_use]
196 pub const fn unacknowledged(self) -> u64 {
197 self.unacknowledged
198 }
199}
200
201#[derive(Clone, Copy, Debug, Eq, PartialEq)]
208pub struct MeasurementSummary {
209 samples: u64,
210 total: u64,
211 latest: u64,
212 maximum: u64,
213}
214
215impl MeasurementSummary {
216 const EMPTY: Self = Self {
217 samples: 0,
218 total: 0,
219 latest: 0,
220 maximum: 0,
221 };
222
223 pub(crate) const fn record(&mut self, value: u64) {
224 self.samples = self.samples.saturating_add(1);
225 self.total = self.total.saturating_add(value);
226 self.latest = value;
227 self.maximum = max_u64(self.maximum, value);
228 }
229
230 #[must_use]
232 pub const fn samples(self) -> u64 {
233 self.samples
234 }
235
236 #[must_use]
238 pub const fn total(self) -> u64 {
239 self.total
240 }
241
242 #[must_use]
244 pub const fn latest(self) -> Option<u64> {
245 if self.samples == 0 {
246 None
247 } else {
248 Some(self.latest)
249 }
250 }
251
252 #[must_use]
254 pub const fn maximum(self) -> Option<u64> {
255 if self.samples == 0 {
256 None
257 } else {
258 Some(self.maximum)
259 }
260 }
261}
262
263#[derive(Clone, Copy, Debug, Eq, PartialEq)]
269pub struct MemoryPageExtent {
270 wasm: u64,
271 stable: u64,
272}
273
274impl MemoryPageExtent {
275 const EMPTY: Self = Self { wasm: 0, stable: 0 };
276
277 pub(crate) const fn new(wasm: u64, stable: u64) -> Self {
278 Self { wasm, stable }
279 }
280
281 #[must_use]
283 pub const fn wasm_pages(self) -> u64 {
284 self.wasm
285 }
286
287 #[must_use]
289 pub const fn stable_pages(self) -> u64 {
290 self.stable
291 }
292}
293
294#[derive(Clone, Copy, Debug, Eq, PartialEq)]
297pub struct MemoryPageSample {
298 start: MemoryPageExtent,
299 end: MemoryPageExtent,
300}
301
302impl MemoryPageSample {
303 const EMPTY: Self = Self {
304 start: MemoryPageExtent::EMPTY,
305 end: MemoryPageExtent::EMPTY,
306 };
307
308 pub(crate) const fn new(start: MemoryPageExtent, end: MemoryPageExtent) -> Self {
309 Self { start, end }
310 }
311
312 #[must_use]
314 pub const fn start(self) -> MemoryPageExtent {
315 self.start
316 }
317
318 #[must_use]
320 pub const fn end(self) -> MemoryPageExtent {
321 self.end
322 }
323
324 #[must_use]
329 pub const fn wasm_growth_pages(self) -> u64 {
330 self.end.wasm.saturating_sub(self.start.wasm)
331 }
332
333 #[must_use]
338 pub const fn stable_growth_pages(self) -> u64 {
339 self.end.stable.saturating_sub(self.start.stable)
340 }
341}
342
343#[derive(Clone, Copy, Debug, Eq, PartialEq)]
349pub struct MemoryPageSummary {
350 samples: u64,
351 latest: MemoryPageSample,
352 maximum_wasm_growth: u64,
353 maximum_stable_growth: u64,
354}
355
356impl MemoryPageSummary {
357 const EMPTY: Self = Self {
358 samples: 0,
359 latest: MemoryPageSample::EMPTY,
360 maximum_wasm_growth: 0,
361 maximum_stable_growth: 0,
362 };
363
364 const fn record(&mut self, sample: MemoryPageSample) {
365 self.samples = self.samples.saturating_add(1);
366 self.latest = sample;
367 self.maximum_wasm_growth = max_u64(self.maximum_wasm_growth, sample.wasm_growth_pages());
368 self.maximum_stable_growth =
369 max_u64(self.maximum_stable_growth, sample.stable_growth_pages());
370 }
371
372 #[must_use]
374 pub const fn samples(self) -> u64 {
375 self.samples
376 }
377
378 #[must_use]
380 pub const fn latest(self) -> Option<MemoryPageSample> {
381 if self.samples == 0 {
382 None
383 } else {
384 Some(self.latest)
385 }
386 }
387
388 #[must_use]
390 pub const fn maximum_wasm_growth_pages(self) -> Option<u64> {
391 if self.samples == 0 {
392 None
393 } else {
394 Some(self.maximum_wasm_growth)
395 }
396 }
397
398 #[must_use]
400 pub const fn maximum_stable_growth_pages(self) -> Option<u64> {
401 if self.samples == 0 {
402 None
403 } else {
404 Some(self.maximum_stable_growth)
405 }
406 }
407}
408
409const fn max_u64(left: u64, right: u64) -> u64 {
410 if left > right { left } else { right }
411}
412
413#[derive(Clone, Copy, Debug, Eq, PartialEq)]
424pub struct TimerPerformance {
425 scheduler_instructions: MeasurementSummary,
426 work_instructions: MeasurementSummary,
427 scheduler_memory_pages: MemoryPageSummary,
428 work_memory_pages: MemoryPageSummary,
429}
430
431impl TimerPerformance {
432 const EMPTY: Self = Self {
433 scheduler_instructions: MeasurementSummary::EMPTY,
434 work_instructions: MeasurementSummary::EMPTY,
435 scheduler_memory_pages: MemoryPageSummary::EMPTY,
436 work_memory_pages: MemoryPageSummary::EMPTY,
437 };
438
439 pub(crate) const fn record_scheduler(&mut self, instructions: u64, memory: MemoryPageSample) {
440 self.scheduler_instructions.record(instructions);
441 self.scheduler_memory_pages.record(memory);
442 }
443
444 pub(crate) const fn record_work(&mut self, instructions: u64, memory: MemoryPageSample) {
445 self.work_instructions.record(instructions);
446 self.work_memory_pages.record(memory);
447 }
448
449 #[must_use]
451 pub const fn scheduler_instructions(self) -> MeasurementSummary {
452 self.scheduler_instructions
453 }
454
455 #[must_use]
457 pub const fn work_instructions(self) -> MeasurementSummary {
458 self.work_instructions
459 }
460
461 #[must_use]
463 pub const fn scheduler_memory_pages(self) -> MemoryPageSummary {
464 self.scheduler_memory_pages
465 }
466
467 #[must_use]
469 pub const fn work_memory_pages(self) -> MemoryPageSummary {
470 self.work_memory_pages
471 }
472}
473
474#[derive(Clone, Copy, Debug, Eq, PartialEq)]
476pub struct TimerObservabilitySnapshot {
477 epoch: TimerEpoch,
478 outcomes: TimerOutcomeSnapshot,
479 counters: TimerCounters,
480 performance: TimerPerformance,
481}
482
483impl TimerObservabilitySnapshot {
484 pub(crate) const fn new(epoch: TimerEpoch) -> Self {
485 Self {
486 epoch,
487 outcomes: TimerOutcomeSnapshot::EMPTY,
488 counters: TimerCounters::EMPTY,
489 performance: TimerPerformance::EMPTY,
490 }
491 }
492
493 pub(crate) const fn record_completion(
494 &mut self,
495 completion: TimerCompletion,
496 completed_at_ns: u64,
497 ) {
498 self.outcomes.record_completion(completion, completed_at_ns);
499 self.counters.record_completion(completion.outcome());
500 }
501
502 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
503 self.outcomes.record_unacknowledged(observed_at_ns);
504 self.counters.record_unacknowledged();
505 }
506
507 pub(crate) const fn counters_mut(&mut self) -> &mut TimerCounters {
508 &mut self.counters
509 }
510
511 pub(crate) const fn record_scheduler_measurements(
512 &mut self,
513 instructions: u64,
514 memory: MemoryPageSample,
515 ) {
516 self.performance.record_scheduler(instructions, memory);
517 }
518
519 pub(crate) const fn record_work_measurements(
520 &mut self,
521 instructions: u64,
522 memory: MemoryPageSample,
523 ) {
524 self.performance.record_work(instructions, memory);
525 }
526
527 #[must_use]
529 pub const fn epoch(self) -> TimerEpoch {
530 self.epoch
531 }
532
533 #[must_use]
535 pub const fn outcomes(self) -> TimerOutcomeSnapshot {
536 self.outcomes
537 }
538
539 #[must_use]
541 pub const fn counters(self) -> TimerCounters {
542 self.counters
543 }
544
545 #[must_use]
547 pub const fn performance(self) -> TimerPerformance {
548 self.performance
549 }
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555
556 #[test]
557 fn all_counters_saturate() {
558 let mut counters = TimerCounters {
559 schedule_requests: u64::MAX,
560 wakeups_armed: u64::MAX,
561 work_dispatched: u64::MAX,
562 scheduler_started: u64::MAX,
563 work_started: u64::MAX,
564 succeeded: u64::MAX,
565 no_work: u64::MAX,
566 retryable_failure: u64::MAX,
567 invariant_failure: u64::MAX,
568 cancelled: u64::MAX,
569 stale_wakeups: u64::MAX,
570 stale_work: u64::MAX,
571 coalesced: u64::MAX,
572 unacknowledged: u64::MAX,
573 };
574
575 counters.record_schedule_request();
576 counters.record_wakeup_armed();
577 counters.record_work_dispatched();
578 counters.record_scheduler_started();
579 counters.record_work_started();
580 for outcome in [
581 TimerCompletionOutcome::Success,
582 TimerCompletionOutcome::NoWork,
583 TimerCompletionOutcome::RetryableFailure,
584 TimerCompletionOutcome::InvariantFailure,
585 ] {
586 counters.record_completion(outcome);
587 }
588 counters.record_cancellation();
589 counters.record_stale_wakeup();
590 counters.record_stale_work();
591 counters.record_coalesced();
592 counters.record_unacknowledged();
593
594 assert_eq!(counters.schedule_requests(), u64::MAX);
595 assert_eq!(counters.wakeups_armed(), u64::MAX);
596 assert_eq!(counters.work_dispatched(), u64::MAX);
597 assert_eq!(counters.work_completed(), u64::MAX);
598 assert_eq!(counters.succeeded(), u64::MAX);
599 assert_eq!(counters.no_work(), u64::MAX);
600 assert_eq!(counters.retryable_failure(), u64::MAX);
601 assert_eq!(counters.invariant_failure(), u64::MAX);
602 assert_eq!(counters.cancelled(), u64::MAX);
603 assert_eq!(counters.stale_wakeups(), u64::MAX);
604 assert_eq!(counters.stale_work(), u64::MAX);
605 assert_eq!(counters.coalesced(), u64::MAX);
606 assert_eq!(counters.unacknowledged(), u64::MAX);
607 }
608
609 #[test]
610 fn completion_totals_follow_classifications_and_saturate_across_outcomes() {
611 let mut counters = TimerCounters::EMPTY;
612 assert_eq!(counters.work_completed(), 0);
613 for (outcome, total) in [
614 (TimerCompletionOutcome::Success, 1),
615 (TimerCompletionOutcome::NoWork, 2),
616 (TimerCompletionOutcome::RetryableFailure, 3),
617 (TimerCompletionOutcome::InvariantFailure, 4),
618 ] {
619 counters.record_completion(outcome);
620 assert_eq!(counters.work_completed(), total);
621 }
622 assert_eq!(counters.succeeded(), 1);
623 assert_eq!(counters.no_work(), 1);
624 assert_eq!(counters.retryable_failure(), 1);
625 assert_eq!(counters.invariant_failure(), 1);
626 assert_eq!(counters.work_started(), 0);
627
628 let mut counters = TimerCounters {
629 succeeded: u64::MAX - 2,
630 ..TimerCounters::EMPTY
631 };
632 counters.record_completion(TimerCompletionOutcome::NoWork);
633 assert_eq!(counters.work_completed(), u64::MAX - 1);
634 counters.record_completion(TimerCompletionOutcome::RetryableFailure);
635 assert_eq!(counters.work_completed(), u64::MAX);
636 counters.record_completion(TimerCompletionOutcome::InvariantFailure);
637 counters.record_completion(TimerCompletionOutcome::Success);
638 assert_eq!(counters.work_completed(), u64::MAX);
639 assert_eq!(counters.succeeded(), u64::MAX - 1);
640 assert_eq!(counters.no_work(), 1);
641 assert_eq!(counters.retryable_failure(), 1);
642 assert_eq!(counters.invariant_failure(), 1);
643 }
644
645 #[test]
646 fn instruction_measurements_preserve_empty_zero_and_saturated_observations() {
647 let mut summary = MeasurementSummary::EMPTY;
648 assert_eq!(summary.samples(), 0);
649 assert_eq!(summary.total(), 0);
650 assert_eq!(summary.latest(), None);
651 assert_eq!(summary.maximum(), None);
652
653 summary.record(0);
654 assert_eq!(summary.samples(), 1);
655 assert_eq!(summary.latest(), Some(0));
656 assert_eq!(summary.maximum(), Some(0));
657
658 summary.record(u64::MAX);
659 summary.record(1);
660 assert_eq!(summary.samples(), 3);
661 assert_eq!(summary.total(), u64::MAX);
662 assert_eq!(summary.latest(), Some(1));
663 assert_eq!(summary.maximum(), Some(u64::MAX));
664
665 summary.samples = u64::MAX;
666 summary.record(2);
667 assert_eq!(summary.samples(), u64::MAX);
668 assert_eq!(summary.total(), u64::MAX);
669 assert_eq!(summary.latest(), Some(2));
670 assert_eq!(summary.maximum(), Some(u64::MAX));
671 }
672
673 #[test]
674 fn callback_measurements_are_role_specific_bounded_and_saturating() {
675 let mut performance = TimerPerformance::EMPTY;
676 performance.record_scheduler(
677 20,
678 MemoryPageSample::new(MemoryPageExtent::new(1, 2), MemoryPageExtent::new(2, 4)),
679 );
680 performance.record_work(
681 30,
682 MemoryPageSample::new(MemoryPageExtent::new(2, 4), MemoryPageExtent::new(5, 5)),
683 );
684 performance.record_work(
685 10,
686 MemoryPageSample::new(MemoryPageExtent::new(5, 5), MemoryPageExtent::new(6, 9)),
687 );
688
689 assert_eq!(performance.scheduler_instructions().total(), 20);
690 assert_eq!(performance.work_instructions().samples(), 2);
691 assert_eq!(performance.work_instructions().total(), 40);
692 assert_eq!(performance.work_instructions().latest(), Some(10));
693 assert_eq!(performance.work_instructions().maximum(), Some(30));
694
695 let scheduler_memory = performance.scheduler_memory_pages();
696 assert_eq!(
697 scheduler_memory.samples(),
698 performance.scheduler_instructions().samples()
699 );
700 assert_eq!(scheduler_memory.samples(), 1);
701 let scheduler_latest = scheduler_memory
702 .latest()
703 .expect("scheduler sample should exist");
704 assert_eq!(scheduler_latest.start().wasm_pages(), 1);
705 assert_eq!(scheduler_latest.start().stable_pages(), 2);
706 assert_eq!(scheduler_latest.end().wasm_pages(), 2);
707 assert_eq!(scheduler_latest.end().stable_pages(), 4);
708 assert_eq!(scheduler_latest.wasm_growth_pages(), 1);
709 assert_eq!(scheduler_latest.stable_growth_pages(), 2);
710
711 let work_memory = performance.work_memory_pages();
712 assert_eq!(
713 work_memory.samples(),
714 performance.work_instructions().samples()
715 );
716 assert_eq!(work_memory.samples(), 2);
717 let work_latest = work_memory.latest().expect("work sample should exist");
718 assert_eq!(work_latest.wasm_growth_pages(), 1);
719 assert_eq!(work_latest.stable_growth_pages(), 4);
720 assert_eq!(work_memory.maximum_wasm_growth_pages(), Some(3));
721 assert_eq!(work_memory.maximum_stable_growth_pages(), Some(4));
722 }
723
724 #[test]
725 fn memory_sample_count_saturates_while_latest_and_maximum_continue() {
726 let mut summary = MemoryPageSummary {
727 samples: u64::MAX,
728 latest: MemoryPageSample::EMPTY,
729 maximum_wasm_growth: 1,
730 maximum_stable_growth: 1,
731 };
732 let sample =
733 MemoryPageSample::new(MemoryPageExtent::new(10, 20), MemoryPageExtent::new(13, 25));
734
735 summary.record(sample);
736
737 assert_eq!(summary.samples(), u64::MAX);
738 assert_eq!(summary.latest(), Some(sample));
739 assert_eq!(summary.maximum_wasm_growth_pages(), Some(3));
740 assert_eq!(summary.maximum_stable_growth_pages(), Some(5));
741 }
742}