1use super::{TimerCompletion, TimerCompletionOutcome, TimerOutcomeSnapshot};
4use ic_metrics::{MeasurementCounter, MeasurementSummary};
5
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct TimerCounters {
18 schedule_requests: MeasurementCounter,
19 wakeups_armed: MeasurementCounter,
20 work_dispatched: MeasurementCounter,
21 scheduler_started: MeasurementCounter,
22 work_started: MeasurementCounter,
23 succeeded: MeasurementCounter,
24 no_work: MeasurementCounter,
25 retryable_failure: MeasurementCounter,
26 invariant_failure: MeasurementCounter,
27 cancelled: MeasurementCounter,
28 stale_wakeups: MeasurementCounter,
29 stale_work: MeasurementCounter,
30 coalesced: MeasurementCounter,
31 unacknowledged: MeasurementCounter,
32}
33
34impl TimerCounters {
35 const EMPTY: Self = Self {
36 schedule_requests: MeasurementCounter::ZERO,
37 wakeups_armed: MeasurementCounter::ZERO,
38 work_dispatched: MeasurementCounter::ZERO,
39 scheduler_started: MeasurementCounter::ZERO,
40 work_started: MeasurementCounter::ZERO,
41 succeeded: MeasurementCounter::ZERO,
42 no_work: MeasurementCounter::ZERO,
43 retryable_failure: MeasurementCounter::ZERO,
44 invariant_failure: MeasurementCounter::ZERO,
45 cancelled: MeasurementCounter::ZERO,
46 stale_wakeups: MeasurementCounter::ZERO,
47 stale_work: MeasurementCounter::ZERO,
48 coalesced: MeasurementCounter::ZERO,
49 unacknowledged: MeasurementCounter::ZERO,
50 };
51
52 pub(crate) const fn record_schedule_request(&mut self) {
53 self.schedule_requests.add(1);
54 }
55
56 pub(crate) const fn record_wakeup_armed(&mut self) {
57 self.wakeups_armed.add(1);
58 }
59
60 pub(crate) const fn record_work_dispatched(&mut self) {
61 self.work_dispatched.add(1);
62 }
63
64 pub(crate) const fn record_scheduler_started(&mut self) {
65 self.scheduler_started.add(1);
66 }
67
68 pub(crate) const fn record_work_started(&mut self) {
69 self.work_started.add(1);
70 }
71
72 pub(crate) const fn record_completion(&mut self, outcome: TimerCompletionOutcome) {
73 match outcome {
74 TimerCompletionOutcome::Success => {
75 self.succeeded.add(1);
76 }
77 TimerCompletionOutcome::NoWork => {
78 self.no_work.add(1);
79 }
80 TimerCompletionOutcome::RetryableFailure => {
81 self.retryable_failure.add(1);
82 }
83 TimerCompletionOutcome::InvariantFailure => {
84 self.invariant_failure.add(1);
85 }
86 }
87 }
88
89 pub(crate) const fn record_cancellation(&mut self) {
90 self.cancelled.add(1);
91 }
92
93 pub(crate) const fn record_stale_wakeup(&mut self) {
94 self.stale_wakeups.add(1);
95 }
96
97 pub(crate) const fn record_stale_work(&mut self) {
98 self.stale_work.add(1);
99 }
100
101 pub(crate) const fn record_coalesced(&mut self) {
102 self.coalesced.add(1);
103 }
104
105 pub(crate) const fn record_unacknowledged(&mut self) {
106 self.unacknowledged.add(1);
107 }
108
109 #[must_use]
111 pub const fn schedule_requests(self) -> MeasurementCounter {
112 self.schedule_requests
113 }
114
115 #[must_use]
117 pub const fn wakeups_armed(self) -> MeasurementCounter {
118 self.wakeups_armed
119 }
120
121 #[must_use]
123 pub const fn work_dispatched(self) -> MeasurementCounter {
124 self.work_dispatched
125 }
126
127 #[must_use]
129 pub const fn scheduler_started(self) -> MeasurementCounter {
130 self.scheduler_started
131 }
132
133 #[must_use]
135 pub const fn work_started(self) -> MeasurementCounter {
136 self.work_started
137 }
138
139 #[must_use]
143 pub const fn work_completed(self) -> MeasurementCounter {
144 let mut completed = self.succeeded;
145 completed.merge(self.no_work);
146 completed.merge(self.retryable_failure);
147 completed.merge(self.invariant_failure);
148 completed
149 }
150
151 #[must_use]
153 pub const fn succeeded(self) -> MeasurementCounter {
154 self.succeeded
155 }
156
157 #[must_use]
159 pub const fn no_work(self) -> MeasurementCounter {
160 self.no_work
161 }
162
163 #[must_use]
165 pub const fn retryable_failure(self) -> MeasurementCounter {
166 self.retryable_failure
167 }
168
169 #[must_use]
171 pub const fn invariant_failure(self) -> MeasurementCounter {
172 self.invariant_failure
173 }
174
175 #[must_use]
177 pub const fn cancelled(self) -> MeasurementCounter {
178 self.cancelled
179 }
180
181 #[must_use]
183 pub const fn stale_wakeups(self) -> MeasurementCounter {
184 self.stale_wakeups
185 }
186
187 #[must_use]
189 pub const fn stale_work(self) -> MeasurementCounter {
190 self.stale_work
191 }
192
193 #[must_use]
195 pub const fn coalesced(self) -> MeasurementCounter {
196 self.coalesced
197 }
198
199 #[must_use]
202 pub const fn unacknowledged(self) -> MeasurementCounter {
203 self.unacknowledged
204 }
205}
206
207#[derive(Clone, Copy, Debug, Eq, PartialEq)]
213pub struct MemoryPageExtent {
214 wasm: u64,
215 stable: u64,
216}
217
218impl MemoryPageExtent {
219 const EMPTY: Self = Self { wasm: 0, stable: 0 };
220
221 pub(crate) const fn new(wasm: u64, stable: u64) -> Self {
222 Self { wasm, stable }
223 }
224
225 #[must_use]
227 pub const fn wasm_pages(self) -> u64 {
228 self.wasm
229 }
230
231 #[must_use]
233 pub const fn stable_pages(self) -> u64 {
234 self.stable
235 }
236}
237
238#[derive(Clone, Copy, Debug, Eq, PartialEq)]
241pub struct MemoryPageSample {
242 start: MemoryPageExtent,
243 end: MemoryPageExtent,
244}
245
246impl MemoryPageSample {
247 const EMPTY: Self = Self {
248 start: MemoryPageExtent::EMPTY,
249 end: MemoryPageExtent::EMPTY,
250 };
251
252 pub(crate) const fn new(start: MemoryPageExtent, end: MemoryPageExtent) -> Self {
253 Self { start, end }
254 }
255
256 #[must_use]
258 pub const fn start(self) -> MemoryPageExtent {
259 self.start
260 }
261
262 #[must_use]
264 pub const fn end(self) -> MemoryPageExtent {
265 self.end
266 }
267
268 #[must_use]
273 pub const fn wasm_growth_pages(self) -> u64 {
274 self.end.wasm.saturating_sub(self.start.wasm)
275 }
276
277 #[must_use]
282 pub const fn stable_growth_pages(self) -> u64 {
283 self.end.stable.saturating_sub(self.start.stable)
284 }
285}
286
287#[derive(Clone, Copy, Debug, Eq, PartialEq)]
293pub struct MemoryPageSummary {
294 samples: u64,
295 latest: MemoryPageSample,
296 maximum_wasm_growth: u64,
297 maximum_stable_growth: u64,
298}
299
300impl MemoryPageSummary {
301 const EMPTY: Self = Self {
302 samples: 0,
303 latest: MemoryPageSample::EMPTY,
304 maximum_wasm_growth: 0,
305 maximum_stable_growth: 0,
306 };
307
308 const fn record(&mut self, sample: MemoryPageSample) {
309 self.samples = self.samples.saturating_add(1);
310 self.latest = sample;
311 self.maximum_wasm_growth = max_u64(self.maximum_wasm_growth, sample.wasm_growth_pages());
312 self.maximum_stable_growth =
313 max_u64(self.maximum_stable_growth, sample.stable_growth_pages());
314 }
315
316 #[must_use]
318 pub const fn samples(self) -> u64 {
319 self.samples
320 }
321
322 #[must_use]
324 pub const fn latest(self) -> Option<MemoryPageSample> {
325 if self.samples == 0 {
326 None
327 } else {
328 Some(self.latest)
329 }
330 }
331
332 #[must_use]
334 pub const fn maximum_wasm_growth_pages(self) -> Option<u64> {
335 if self.samples == 0 {
336 None
337 } else {
338 Some(self.maximum_wasm_growth)
339 }
340 }
341
342 #[must_use]
344 pub const fn maximum_stable_growth_pages(self) -> Option<u64> {
345 if self.samples == 0 {
346 None
347 } else {
348 Some(self.maximum_stable_growth)
349 }
350 }
351}
352
353const fn max_u64(left: u64, right: u64) -> u64 {
354 if left > right { left } else { right }
355}
356
357#[derive(Clone, Copy, Debug, Eq, PartialEq)]
375pub struct TimerPerformance {
376 scheduler_instructions: MeasurementSummary,
377 work_instructions: MeasurementSummary,
378 scheduler_memory_pages: MemoryPageSummary,
379 work_memory_pages: MemoryPageSummary,
380}
381
382impl TimerPerformance {
383 const EMPTY: Self = Self {
384 scheduler_instructions: MeasurementSummary::EMPTY,
385 work_instructions: MeasurementSummary::EMPTY,
386 scheduler_memory_pages: MemoryPageSummary::EMPTY,
387 work_memory_pages: MemoryPageSummary::EMPTY,
388 };
389
390 pub(crate) const fn record_scheduler(&mut self, instructions: u64, memory: MemoryPageSample) {
391 self.scheduler_instructions.record(instructions);
392 self.scheduler_memory_pages.record(memory);
393 }
394
395 pub(crate) const fn record_work(&mut self, instructions: u64, memory: MemoryPageSample) {
396 self.work_instructions.record(instructions);
397 self.work_memory_pages.record(memory);
398 }
399
400 #[must_use]
402 pub const fn scheduler_instructions(self) -> MeasurementSummary {
403 self.scheduler_instructions
404 }
405
406 #[must_use]
408 pub const fn work_instructions(self) -> MeasurementSummary {
409 self.work_instructions
410 }
411
412 #[must_use]
414 pub const fn scheduler_memory_pages(self) -> MemoryPageSummary {
415 self.scheduler_memory_pages
416 }
417
418 #[must_use]
420 pub const fn work_memory_pages(self) -> MemoryPageSummary {
421 self.work_memory_pages
422 }
423}
424
425#[derive(Clone, Copy, Debug, Eq, PartialEq)]
430pub struct TimerObservabilitySnapshot {
431 outcomes: TimerOutcomeSnapshot,
432 counters: TimerCounters,
433 performance: TimerPerformance,
434}
435
436impl TimerObservabilitySnapshot {
437 pub(crate) const EMPTY: Self = Self {
438 outcomes: TimerOutcomeSnapshot::EMPTY,
439 counters: TimerCounters::EMPTY,
440 performance: TimerPerformance::EMPTY,
441 };
442
443 pub(crate) const fn record_completion(
444 &mut self,
445 completion: TimerCompletion,
446 completed_at_ns: u64,
447 ) {
448 self.outcomes.record_completion(completion, completed_at_ns);
449 self.counters.record_completion(completion.outcome());
450 }
451
452 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
453 self.outcomes.record_unacknowledged(observed_at_ns);
454 self.counters.record_unacknowledged();
455 }
456
457 pub(crate) const fn counters_mut(&mut self) -> &mut TimerCounters {
458 &mut self.counters
459 }
460
461 pub(crate) const fn record_scheduler_measurements(
462 &mut self,
463 instructions: u64,
464 memory: MemoryPageSample,
465 ) {
466 self.performance.record_scheduler(instructions, memory);
467 }
468
469 pub(crate) const fn record_work_measurements(
470 &mut self,
471 instructions: u64,
472 memory: MemoryPageSample,
473 ) {
474 self.performance.record_work(instructions, memory);
475 }
476
477 #[must_use]
479 pub const fn outcomes(self) -> TimerOutcomeSnapshot {
480 self.outcomes
481 }
482
483 #[must_use]
485 pub const fn counters(self) -> TimerCounters {
486 self.counters
487 }
488
489 #[must_use]
491 pub const fn performance(self) -> TimerPerformance {
492 self.performance
493 }
494}
495
496#[cfg(test)]
497mod tests {
498 use super::*;
499
500 #[test]
501 fn all_counters_saturate() {
502 let exact_cap = MeasurementCounter::from_parts(u128::MAX, false).unwrap();
503 let mut counters = TimerCounters {
504 schedule_requests: exact_cap,
505 wakeups_armed: exact_cap,
506 work_dispatched: exact_cap,
507 scheduler_started: exact_cap,
508 work_started: exact_cap,
509 succeeded: exact_cap,
510 no_work: exact_cap,
511 retryable_failure: exact_cap,
512 invariant_failure: exact_cap,
513 cancelled: exact_cap,
514 stale_wakeups: exact_cap,
515 stale_work: exact_cap,
516 coalesced: exact_cap,
517 unacknowledged: exact_cap,
518 };
519 assert!(!counters.schedule_requests().total_overflowed());
520 counters.record_schedule_request();
521 counters.record_wakeup_armed();
522 counters.record_work_dispatched();
523 counters.record_scheduler_started();
524 counters.record_work_started();
525 for outcome in [
526 TimerCompletionOutcome::Success,
527 TimerCompletionOutcome::NoWork,
528 TimerCompletionOutcome::RetryableFailure,
529 TimerCompletionOutcome::InvariantFailure,
530 ] {
531 counters.record_completion(outcome);
532 }
533 counters.record_cancellation();
534 counters.record_stale_wakeup();
535 counters.record_stale_work();
536 counters.record_coalesced();
537 counters.record_unacknowledged();
538
539 let mut expected = exact_cap;
541 expected.add(1);
542 for observed in [
543 counters.schedule_requests(),
544 counters.wakeups_armed(),
545 counters.work_dispatched(),
546 counters.scheduler_started(),
547 counters.work_started(),
548 counters.succeeded(),
549 counters.no_work(),
550 counters.retryable_failure(),
551 counters.invariant_failure(),
552 counters.cancelled(),
553 counters.stale_wakeups(),
554 counters.stale_work(),
555 counters.coalesced(),
556 counters.unacknowledged(),
557 ] {
558 assert_eq!(observed, expected);
559 }
560 }
561
562 #[test]
563 fn completion_totals_follow_classifications_and_saturate_across_outcomes() {
564 let mut counters = TimerCounters::EMPTY;
565 assert_eq!(counters.work_completed().exact_total(), Ok(0));
566 for (outcome, total) in [
567 (TimerCompletionOutcome::Success, 1),
568 (TimerCompletionOutcome::NoWork, 2),
569 (TimerCompletionOutcome::RetryableFailure, 3),
570 (TimerCompletionOutcome::InvariantFailure, 4),
571 ] {
572 counters.record_completion(outcome);
573 assert_eq!(counters.work_completed().exact_total(), Ok(total));
574 }
575 assert_eq!(counters.succeeded().exact_total(), Ok(1));
576 assert_eq!(counters.no_work().exact_total(), Ok(1));
577 assert_eq!(counters.retryable_failure().exact_total(), Ok(1));
578 assert_eq!(counters.invariant_failure().exact_total(), Ok(1));
579 assert_eq!(counters.work_started().exact_total(), Ok(0));
580
581 let mut counters = TimerCounters {
582 succeeded: MeasurementCounter::from_parts(u128::MAX - 2, false).unwrap(),
583 ..TimerCounters::EMPTY
584 };
585 counters.record_completion(TimerCompletionOutcome::NoWork);
586 assert_eq!(counters.work_completed().exact_total(), Ok(u128::MAX - 1));
587 counters.record_completion(TimerCompletionOutcome::RetryableFailure);
588 assert_eq!(counters.work_completed().exact_total(), Ok(u128::MAX));
589 counters.record_completion(TimerCompletionOutcome::InvariantFailure);
590 assert!(counters.work_completed().exact_total().is_err());
591 counters = TimerCounters {
593 succeeded: MeasurementCounter::from_parts(u128::MAX, true).unwrap(),
594 ..TimerCounters::EMPTY
595 };
596 assert!(counters.work_completed().total_overflowed());
597 }
598
599 #[test]
600 fn counter_copies_cross_the_old_cap_without_mutating_registration_state() {
601 let mut counters = TimerCounters {
602 schedule_requests: MeasurementCounter::from_parts(u128::from(u64::MAX), false).unwrap(),
603 ..TimerCounters::EMPTY
604 };
605 let previous = counters.schedule_requests();
606 counters.record_schedule_request();
607 assert_eq!(
608 counters.schedule_requests().exact_total(),
609 Ok(u128::from(u64::MAX) + 1)
610 );
611 assert_eq!(counters.schedule_requests().delta_since(previous), Ok(1));
612 let mut copied = counters.schedule_requests();
613 copied.add(10);
614 assert_eq!(counters.schedule_requests().delta_since(previous), Ok(1));
615 }
616
617 #[test]
618 fn callback_measurements_are_role_specific_bounded_and_saturating() {
619 let mut performance = TimerPerformance::EMPTY;
620 performance.record_scheduler(
621 20,
622 MemoryPageSample::new(MemoryPageExtent::new(1, 2), MemoryPageExtent::new(2, 4)),
623 );
624 performance.record_work(
625 30,
626 MemoryPageSample::new(MemoryPageExtent::new(2, 4), MemoryPageExtent::new(5, 5)),
627 );
628 performance.record_work(
629 10,
630 MemoryPageSample::new(MemoryPageExtent::new(5, 5), MemoryPageExtent::new(6, 9)),
631 );
632
633 assert_eq!(performance.scheduler_instructions().aggregate().total(), 20);
634 assert_eq!(performance.work_instructions().aggregate().samples(), 2);
635 assert_eq!(performance.work_instructions().aggregate().total(), 40);
636 assert_eq!(performance.work_instructions().latest(), Some(10));
637 assert_eq!(
638 performance.work_instructions().aggregate().maximum(),
639 Some(30)
640 );
641
642 let scheduler_memory = performance.scheduler_memory_pages();
643 assert_eq!(
644 scheduler_memory.samples(),
645 performance.scheduler_instructions().aggregate().samples()
646 );
647 assert_eq!(scheduler_memory.samples(), 1);
648 let scheduler_latest = scheduler_memory
649 .latest()
650 .expect("scheduler sample should exist");
651 assert_eq!(scheduler_latest.start().wasm_pages(), 1);
652 assert_eq!(scheduler_latest.start().stable_pages(), 2);
653 assert_eq!(scheduler_latest.end().wasm_pages(), 2);
654 assert_eq!(scheduler_latest.end().stable_pages(), 4);
655 assert_eq!(scheduler_latest.wasm_growth_pages(), 1);
656 assert_eq!(scheduler_latest.stable_growth_pages(), 2);
657
658 let work_memory = performance.work_memory_pages();
659 assert_eq!(
660 work_memory.samples(),
661 performance.work_instructions().aggregate().samples()
662 );
663 assert_eq!(work_memory.samples(), 2);
664 let work_latest = work_memory.latest().expect("work sample should exist");
665 assert_eq!(work_latest.wasm_growth_pages(), 1);
666 assert_eq!(work_latest.stable_growth_pages(), 4);
667 assert_eq!(work_memory.maximum_wasm_growth_pages(), Some(3));
668 assert_eq!(work_memory.maximum_stable_growth_pages(), Some(4));
669 }
670
671 #[test]
672 fn instruction_summaries_keep_empty_zero_and_role_boundaries() {
673 let mut performance = TimerPerformance::EMPTY;
674 let mut expected = MeasurementSummary::EMPTY;
675 assert_eq!(performance.scheduler_instructions(), expected);
676 assert_eq!(performance.work_instructions(), expected);
677
678 for instructions in [0, u64::MAX, 1, u64::MAX] {
680 let previous_work = performance.work_instructions();
681 expected.record(instructions);
682 performance.record_scheduler(instructions, MemoryPageSample::EMPTY);
683 assert_eq!(performance.scheduler_instructions(), expected);
684 assert_eq!(performance.work_instructions(), previous_work);
685
686 performance.record_work(instructions, MemoryPageSample::EMPTY);
687 assert_eq!(performance.work_instructions(), expected);
688 assert_eq!(performance.scheduler_instructions(), expected);
689 }
690 }
691
692 #[test]
693 fn memory_sample_count_saturates_while_latest_and_maximum_continue() {
694 let mut summary = MemoryPageSummary {
695 samples: u64::MAX,
696 latest: MemoryPageSample::EMPTY,
697 maximum_wasm_growth: 1,
698 maximum_stable_growth: 1,
699 };
700 let sample =
701 MemoryPageSample::new(MemoryPageExtent::new(10, 20), MemoryPageExtent::new(13, 25));
702
703 summary.record(sample);
704
705 assert_eq!(summary.samples(), u64::MAX);
706 assert_eq!(summary.latest(), Some(sample));
707 assert_eq!(summary.maximum_wasm_growth_pages(), Some(3));
708 assert_eq!(summary.maximum_stable_growth_pages(), Some(5));
709 }
710}