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 work_completed: u64,
20 succeeded: u64,
21 no_work: u64,
22 retryable_failure: u64,
23 invariant_failure: u64,
24 cancelled: u64,
25 stale_wakeups: u64,
26 stale_work: u64,
27 coalesced: u64,
28 unacknowledged: u64,
29}
30
31impl TimerCounters {
32 const EMPTY: Self = Self {
33 schedule_requests: 0,
34 wakeups_armed: 0,
35 work_dispatched: 0,
36 scheduler_started: 0,
37 work_started: 0,
38 work_completed: 0,
39 succeeded: 0,
40 no_work: 0,
41 retryable_failure: 0,
42 invariant_failure: 0,
43 cancelled: 0,
44 stale_wakeups: 0,
45 stale_work: 0,
46 coalesced: 0,
47 unacknowledged: 0,
48 };
49
50 pub(crate) const fn record_schedule_request(&mut self) {
51 self.schedule_requests = self.schedule_requests.saturating_add(1);
52 }
53
54 pub(crate) const fn record_wakeup_armed(&mut self) {
55 self.wakeups_armed = self.wakeups_armed.saturating_add(1);
56 }
57
58 pub(crate) const fn record_work_dispatched(&mut self) {
59 self.work_dispatched = self.work_dispatched.saturating_add(1);
60 }
61
62 pub(crate) const fn record_scheduler_started(&mut self) {
63 self.scheduler_started = self.scheduler_started.saturating_add(1);
64 }
65
66 pub(crate) const fn record_work_started(&mut self) {
67 self.work_started = self.work_started.saturating_add(1);
68 }
69
70 pub(crate) const fn record_completion(&mut self, outcome: TimerCompletionOutcome) {
71 self.work_completed = self.work_completed.saturating_add(1);
72 match outcome {
73 TimerCompletionOutcome::Success => {
74 self.succeeded = self.succeeded.saturating_add(1);
75 }
76 TimerCompletionOutcome::NoWork => {
77 self.no_work = self.no_work.saturating_add(1);
78 }
79 TimerCompletionOutcome::RetryableFailure => {
80 self.retryable_failure = self.retryable_failure.saturating_add(1);
81 }
82 TimerCompletionOutcome::InvariantFailure => {
83 self.invariant_failure = self.invariant_failure.saturating_add(1);
84 }
85 }
86 }
87
88 pub(crate) const fn record_cancellation(&mut self) {
89 self.cancelled = self.cancelled.saturating_add(1);
90 }
91
92 pub(crate) const fn record_stale_wakeup(&mut self) {
93 self.stale_wakeups = self.stale_wakeups.saturating_add(1);
94 }
95
96 pub(crate) const fn record_stale_work(&mut self) {
97 self.stale_work = self.stale_work.saturating_add(1);
98 }
99
100 pub(crate) const fn record_coalesced(&mut self) {
101 self.coalesced = self.coalesced.saturating_add(1);
102 }
103
104 pub(crate) const fn record_unacknowledged(&mut self) {
105 self.unacknowledged = self.unacknowledged.saturating_add(1);
106 }
107
108 #[must_use]
110 pub const fn schedule_requests(self) -> u64 {
111 self.schedule_requests
112 }
113
114 #[must_use]
116 pub const fn wakeups_armed(self) -> u64 {
117 self.wakeups_armed
118 }
119
120 #[must_use]
122 pub const fn work_dispatched(self) -> u64 {
123 self.work_dispatched
124 }
125
126 #[must_use]
128 pub const fn scheduler_started(self) -> u64 {
129 self.scheduler_started
130 }
131
132 #[must_use]
134 pub const fn work_started(self) -> u64 {
135 self.work_started
136 }
137
138 #[must_use]
140 pub const fn work_completed(self) -> u64 {
141 self.work_completed
142 }
143
144 #[must_use]
146 pub const fn succeeded(self) -> u64 {
147 self.succeeded
148 }
149
150 #[must_use]
152 pub const fn no_work(self) -> u64 {
153 self.no_work
154 }
155
156 #[must_use]
158 pub const fn retryable_failure(self) -> u64 {
159 self.retryable_failure
160 }
161
162 #[must_use]
164 pub const fn invariant_failure(self) -> u64 {
165 self.invariant_failure
166 }
167
168 #[must_use]
170 pub const fn cancelled(self) -> u64 {
171 self.cancelled
172 }
173
174 #[must_use]
176 pub const fn stale_wakeups(self) -> u64 {
177 self.stale_wakeups
178 }
179
180 #[must_use]
182 pub const fn stale_work(self) -> u64 {
183 self.stale_work
184 }
185
186 #[must_use]
188 pub const fn coalesced(self) -> u64 {
189 self.coalesced
190 }
191
192 #[must_use]
194 pub const fn unacknowledged(self) -> u64 {
195 self.unacknowledged
196 }
197
198 #[must_use]
200 #[cfg(test)]
201 pub(crate) const fn completion_partition_is_valid(self) -> bool {
202 self.work_completed
203 == self
204 .succeeded
205 .saturating_add(self.no_work)
206 .saturating_add(self.retryable_failure)
207 .saturating_add(self.invariant_failure)
208 }
209}
210
211#[derive(Clone, Copy, Debug, Eq, PartialEq)]
218pub struct MeasurementSummary {
219 samples: u64,
220 total: u64,
221 latest: Option<u64>,
222 maximum: Option<u64>,
223}
224
225impl MeasurementSummary {
226 const EMPTY: Self = Self {
227 samples: 0,
228 total: 0,
229 latest: None,
230 maximum: None,
231 };
232
233 pub(crate) const fn record(&mut self, value: u64) {
234 self.samples = self.samples.saturating_add(1);
235 self.total = self.total.saturating_add(value);
236 self.latest = Some(value);
237 self.maximum = Some(match self.maximum {
238 Some(current) if current > value => current,
239 Some(_) | None => value,
240 });
241 }
242
243 #[must_use]
245 pub const fn samples(self) -> u64 {
246 self.samples
247 }
248
249 #[must_use]
251 pub const fn total(self) -> u64 {
252 self.total
253 }
254
255 #[must_use]
257 pub const fn latest(self) -> Option<u64> {
258 self.latest
259 }
260
261 #[must_use]
263 pub const fn maximum(self) -> Option<u64> {
264 self.maximum
265 }
266}
267
268#[derive(Clone, Copy, Debug, Eq, PartialEq)]
274pub struct MemoryPageExtent {
275 wasm: u64,
276 stable: u64,
277}
278
279impl MemoryPageExtent {
280 const EMPTY: Self = Self { wasm: 0, stable: 0 };
281
282 pub(crate) const fn new(wasm: u64, stable: u64) -> Self {
283 Self { wasm, stable }
284 }
285
286 #[must_use]
288 pub const fn wasm_pages(self) -> u64 {
289 self.wasm
290 }
291
292 #[must_use]
294 pub const fn stable_pages(self) -> u64 {
295 self.stable
296 }
297}
298
299#[derive(Clone, Copy, Debug, Eq, PartialEq)]
302pub struct MemoryPageSample {
303 start: MemoryPageExtent,
304 end: MemoryPageExtent,
305}
306
307impl MemoryPageSample {
308 const EMPTY: Self = Self {
309 start: MemoryPageExtent::EMPTY,
310 end: MemoryPageExtent::EMPTY,
311 };
312
313 pub(crate) const fn new(start: MemoryPageExtent, end: MemoryPageExtent) -> Self {
314 Self { start, end }
315 }
316
317 #[must_use]
319 pub const fn start(self) -> MemoryPageExtent {
320 self.start
321 }
322
323 #[must_use]
325 pub const fn end(self) -> MemoryPageExtent {
326 self.end
327 }
328
329 #[must_use]
334 pub const fn wasm_growth_pages(self) -> u64 {
335 self.end.wasm.saturating_sub(self.start.wasm)
336 }
337
338 #[must_use]
343 pub const fn stable_growth_pages(self) -> u64 {
344 self.end.stable.saturating_sub(self.start.stable)
345 }
346}
347
348#[derive(Clone, Copy, Debug, Eq, PartialEq)]
354pub struct MemoryPageSummary {
355 samples: u64,
356 latest: MemoryPageSample,
357 maximum_wasm_growth: u64,
358 maximum_stable_growth: u64,
359}
360
361impl MemoryPageSummary {
362 const EMPTY: Self = Self {
363 samples: 0,
364 latest: MemoryPageSample::EMPTY,
365 maximum_wasm_growth: 0,
366 maximum_stable_growth: 0,
367 };
368
369 const fn record(&mut self, sample: MemoryPageSample) {
370 self.samples = self.samples.saturating_add(1);
371 self.latest = sample;
372 self.maximum_wasm_growth = max_u64(self.maximum_wasm_growth, sample.wasm_growth_pages());
373 self.maximum_stable_growth =
374 max_u64(self.maximum_stable_growth, sample.stable_growth_pages());
375 }
376
377 #[must_use]
379 pub const fn samples(self) -> u64 {
380 self.samples
381 }
382
383 #[must_use]
385 pub const fn latest(self) -> Option<MemoryPageSample> {
386 if self.samples == 0 {
387 None
388 } else {
389 Some(self.latest)
390 }
391 }
392
393 #[must_use]
395 pub const fn maximum_wasm_growth_pages(self) -> Option<u64> {
396 if self.samples == 0 {
397 None
398 } else {
399 Some(self.maximum_wasm_growth)
400 }
401 }
402
403 #[must_use]
405 pub const fn maximum_stable_growth_pages(self) -> Option<u64> {
406 if self.samples == 0 {
407 None
408 } else {
409 Some(self.maximum_stable_growth)
410 }
411 }
412}
413
414const fn max_u64(left: u64, right: u64) -> u64 {
415 if left > right { left } else { right }
416}
417
418#[derive(Clone, Copy, Debug, Eq, PartialEq)]
429pub struct TimerPerformance {
430 scheduler_instructions: MeasurementSummary,
431 work_instructions: MeasurementSummary,
432 scheduler_memory_pages: MemoryPageSummary,
433 work_memory_pages: MemoryPageSummary,
434}
435
436impl TimerPerformance {
437 const EMPTY: Self = Self {
438 scheduler_instructions: MeasurementSummary::EMPTY,
439 work_instructions: MeasurementSummary::EMPTY,
440 scheduler_memory_pages: MemoryPageSummary::EMPTY,
441 work_memory_pages: MemoryPageSummary::EMPTY,
442 };
443
444 pub(crate) const fn record_scheduler(&mut self, instructions: u64, memory: MemoryPageSample) {
445 self.scheduler_instructions.record(instructions);
446 self.scheduler_memory_pages.record(memory);
447 }
448
449 pub(crate) const fn record_work(&mut self, instructions: u64, memory: MemoryPageSample) {
450 self.work_instructions.record(instructions);
451 self.work_memory_pages.record(memory);
452 }
453
454 #[must_use]
456 pub const fn scheduler_instructions(self) -> MeasurementSummary {
457 self.scheduler_instructions
458 }
459
460 #[must_use]
462 pub const fn work_instructions(self) -> MeasurementSummary {
463 self.work_instructions
464 }
465
466 #[must_use]
468 pub const fn scheduler_memory_pages(self) -> MemoryPageSummary {
469 self.scheduler_memory_pages
470 }
471
472 #[must_use]
474 pub const fn work_memory_pages(self) -> MemoryPageSummary {
475 self.work_memory_pages
476 }
477}
478
479#[derive(Clone, Copy, Debug, Eq, PartialEq)]
481pub struct TimerObservabilitySnapshot {
482 epoch: TimerEpoch,
483 outcomes: TimerOutcomeSnapshot,
484 counters: TimerCounters,
485 performance: TimerPerformance,
486}
487
488impl TimerObservabilitySnapshot {
489 pub(crate) const fn new(epoch: TimerEpoch) -> Self {
490 Self {
491 epoch,
492 outcomes: TimerOutcomeSnapshot::EMPTY,
493 counters: TimerCounters::EMPTY,
494 performance: TimerPerformance::EMPTY,
495 }
496 }
497
498 pub(crate) const fn record_completion(
499 &mut self,
500 completion: TimerCompletion,
501 completed_at_ns: u64,
502 ) {
503 self.outcomes.record_completion(completion, completed_at_ns);
504 self.counters.record_completion(completion.outcome());
505 }
506
507 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
508 self.outcomes.record_unacknowledged(observed_at_ns);
509 self.counters.record_unacknowledged();
510 }
511
512 pub(crate) const fn counters_mut(&mut self) -> &mut TimerCounters {
513 &mut self.counters
514 }
515
516 pub(crate) const fn record_scheduler_measurements(
517 &mut self,
518 instructions: u64,
519 memory: MemoryPageSample,
520 ) {
521 self.performance.record_scheduler(instructions, memory);
522 }
523
524 pub(crate) const fn record_work_measurements(
525 &mut self,
526 instructions: u64,
527 memory: MemoryPageSample,
528 ) {
529 self.performance.record_work(instructions, memory);
530 }
531
532 #[must_use]
534 pub const fn epoch(self) -> TimerEpoch {
535 self.epoch
536 }
537
538 #[must_use]
540 pub const fn outcomes(self) -> TimerOutcomeSnapshot {
541 self.outcomes
542 }
543
544 #[must_use]
546 pub const fn counters(self) -> TimerCounters {
547 self.counters
548 }
549
550 #[must_use]
552 pub const fn performance(self) -> TimerPerformance {
553 self.performance
554 }
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560
561 #[test]
562 fn all_counters_saturate() {
563 let mut counters = TimerCounters {
564 schedule_requests: u64::MAX,
565 wakeups_armed: u64::MAX,
566 work_dispatched: u64::MAX,
567 scheduler_started: u64::MAX,
568 work_started: u64::MAX,
569 work_completed: u64::MAX,
570 succeeded: u64::MAX,
571 no_work: u64::MAX,
572 retryable_failure: u64::MAX,
573 invariant_failure: u64::MAX,
574 cancelled: u64::MAX,
575 stale_wakeups: u64::MAX,
576 stale_work: u64::MAX,
577 coalesced: u64::MAX,
578 unacknowledged: u64::MAX,
579 };
580
581 counters.record_schedule_request();
582 counters.record_wakeup_armed();
583 counters.record_work_dispatched();
584 counters.record_scheduler_started();
585 counters.record_work_started();
586 counters.record_completion(TimerCompletionOutcome::Success);
587 counters.record_cancellation();
588 counters.record_stale_wakeup();
589 counters.record_stale_work();
590 counters.record_coalesced();
591 counters.record_unacknowledged();
592
593 assert_eq!(counters.schedule_requests(), u64::MAX);
594 assert_eq!(counters.wakeups_armed(), u64::MAX);
595 assert_eq!(counters.work_dispatched(), u64::MAX);
596 assert_eq!(counters.work_completed(), u64::MAX);
597 assert_eq!(counters.cancelled(), u64::MAX);
598 assert_eq!(counters.stale_wakeups(), u64::MAX);
599 assert_eq!(counters.stale_work(), u64::MAX);
600 assert_eq!(counters.coalesced(), u64::MAX);
601 assert_eq!(counters.unacknowledged(), u64::MAX);
602 assert!(counters.completion_partition_is_valid());
603 }
604
605 #[test]
606 fn callback_measurements_are_role_specific_bounded_and_saturating() {
607 let mut performance = TimerPerformance::EMPTY;
608 performance.record_scheduler(
609 20,
610 MemoryPageSample::new(MemoryPageExtent::new(1, 2), MemoryPageExtent::new(2, 4)),
611 );
612 performance.record_work(
613 30,
614 MemoryPageSample::new(MemoryPageExtent::new(2, 4), MemoryPageExtent::new(5, 5)),
615 );
616 performance.record_work(
617 10,
618 MemoryPageSample::new(MemoryPageExtent::new(5, 5), MemoryPageExtent::new(6, 9)),
619 );
620
621 assert_eq!(performance.scheduler_instructions().total(), 20);
622 assert_eq!(performance.work_instructions().samples(), 2);
623 assert_eq!(performance.work_instructions().total(), 40);
624 assert_eq!(performance.work_instructions().latest(), Some(10));
625 assert_eq!(performance.work_instructions().maximum(), Some(30));
626
627 let scheduler_memory = performance.scheduler_memory_pages();
628 assert_eq!(
629 scheduler_memory.samples(),
630 performance.scheduler_instructions().samples()
631 );
632 assert_eq!(scheduler_memory.samples(), 1);
633 let scheduler_latest = scheduler_memory
634 .latest()
635 .expect("scheduler sample should exist");
636 assert_eq!(scheduler_latest.start().wasm_pages(), 1);
637 assert_eq!(scheduler_latest.start().stable_pages(), 2);
638 assert_eq!(scheduler_latest.end().wasm_pages(), 2);
639 assert_eq!(scheduler_latest.end().stable_pages(), 4);
640 assert_eq!(scheduler_latest.wasm_growth_pages(), 1);
641 assert_eq!(scheduler_latest.stable_growth_pages(), 2);
642
643 let work_memory = performance.work_memory_pages();
644 assert_eq!(
645 work_memory.samples(),
646 performance.work_instructions().samples()
647 );
648 assert_eq!(work_memory.samples(), 2);
649 let work_latest = work_memory.latest().expect("work sample should exist");
650 assert_eq!(work_latest.wasm_growth_pages(), 1);
651 assert_eq!(work_latest.stable_growth_pages(), 4);
652 assert_eq!(work_memory.maximum_wasm_growth_pages(), Some(3));
653 assert_eq!(work_memory.maximum_stable_growth_pages(), Some(4));
654 }
655
656 #[test]
657 fn memory_sample_count_saturates_while_latest_and_maximum_continue() {
658 let mut summary = MemoryPageSummary {
659 samples: u64::MAX,
660 latest: MemoryPageSample::EMPTY,
661 maximum_wasm_growth: 1,
662 maximum_stable_growth: 1,
663 };
664 let sample =
665 MemoryPageSample::new(MemoryPageExtent::new(10, 20), MemoryPageExtent::new(13, 25));
666
667 summary.record(sample);
668
669 assert_eq!(summary.samples(), u64::MAX);
670 assert_eq!(summary.latest(), Some(sample));
671 assert_eq!(summary.maximum_wasm_growth_pages(), Some(3));
672 assert_eq!(summary.maximum_stable_growth_pages(), Some(5));
673 }
674}