1use super::{TimerCompletion, TimerCompletionOutcome, TimerEpoch, TimerOutcomeSnapshot};
4
5#[derive(Clone, Copy, Debug, 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 const EMPTY: Self = Self {
30 schedule_requests: 0,
31 wakeups_armed: 0,
32 work_dispatched: 0,
33 scheduler_started: 0,
34 work_started: 0,
35 work_completed: 0,
36 succeeded: 0,
37 no_work: 0,
38 retryable_failure: 0,
39 invariant_failure: 0,
40 cancelled: 0,
41 stale_wakeups: 0,
42 stale_work: 0,
43 coalesced: 0,
44 unacknowledged: 0,
45 };
46
47 pub(crate) const fn record_schedule_request(&mut self) {
48 self.schedule_requests = self.schedule_requests.saturating_add(1);
49 }
50
51 pub(crate) const fn record_wakeup_armed(&mut self) {
52 self.wakeups_armed = self.wakeups_armed.saturating_add(1);
53 }
54
55 pub(crate) const fn record_work_dispatched(&mut self) {
56 self.work_dispatched = self.work_dispatched.saturating_add(1);
57 }
58
59 pub(crate) const fn record_scheduler_started(&mut self) {
60 self.scheduler_started = self.scheduler_started.saturating_add(1);
61 }
62
63 pub(crate) const fn record_work_started(&mut self) {
64 self.work_started = self.work_started.saturating_add(1);
65 }
66
67 pub(crate) const fn record_completion(&mut self, outcome: TimerCompletionOutcome) {
68 self.work_completed = self.work_completed.saturating_add(1);
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]
137 pub const fn work_completed(self) -> u64 {
138 self.work_completed
139 }
140
141 #[must_use]
143 pub const fn succeeded(self) -> u64 {
144 self.succeeded
145 }
146
147 #[must_use]
149 pub const fn no_work(self) -> u64 {
150 self.no_work
151 }
152
153 #[must_use]
155 pub const fn retryable_failure(self) -> u64 {
156 self.retryable_failure
157 }
158
159 #[must_use]
161 pub const fn invariant_failure(self) -> u64 {
162 self.invariant_failure
163 }
164
165 #[must_use]
167 pub const fn cancelled(self) -> u64 {
168 self.cancelled
169 }
170
171 #[must_use]
173 pub const fn stale_wakeups(self) -> u64 {
174 self.stale_wakeups
175 }
176
177 #[must_use]
179 pub const fn stale_work(self) -> u64 {
180 self.stale_work
181 }
182
183 #[must_use]
185 pub const fn coalesced(self) -> u64 {
186 self.coalesced
187 }
188
189 #[must_use]
191 pub const fn unacknowledged(self) -> u64 {
192 self.unacknowledged
193 }
194
195 #[must_use]
197 #[cfg(test)]
198 pub(crate) const fn completion_partition_is_valid(self) -> bool {
199 self.work_completed
200 == self
201 .succeeded
202 .saturating_add(self.no_work)
203 .saturating_add(self.retryable_failure)
204 .saturating_add(self.invariant_failure)
205 }
206}
207
208#[derive(Clone, Copy, Debug, Eq, PartialEq)]
210pub struct MeasurementSummary {
211 samples: u64,
212 total: u64,
213 latest: Option<u64>,
214 maximum: Option<u64>,
215}
216
217impl MeasurementSummary {
218 const EMPTY: Self = Self {
219 samples: 0,
220 total: 0,
221 latest: None,
222 maximum: None,
223 };
224
225 pub(crate) const fn record(&mut self, value: u64) {
226 self.samples = self.samples.saturating_add(1);
227 self.total = self.total.saturating_add(value);
228 self.latest = Some(value);
229 self.maximum = Some(match self.maximum {
230 Some(current) if current > value => current,
231 Some(_) | None => value,
232 });
233 }
234
235 #[must_use]
237 pub const fn samples(self) -> u64 {
238 self.samples
239 }
240
241 #[must_use]
243 pub const fn total(self) -> u64 {
244 self.total
245 }
246
247 #[must_use]
249 pub const fn latest(self) -> Option<u64> {
250 self.latest
251 }
252
253 #[must_use]
255 pub const fn maximum(self) -> Option<u64> {
256 self.maximum
257 }
258}
259
260#[derive(Clone, Copy, Debug, Eq, PartialEq)]
266pub struct MemoryPageExtent {
267 wasm: u64,
268 stable: u64,
269}
270
271impl MemoryPageExtent {
272 const EMPTY: Self = Self { wasm: 0, stable: 0 };
273
274 pub(crate) const fn new(wasm: u64, stable: u64) -> Self {
275 Self { wasm, stable }
276 }
277
278 #[must_use]
280 pub const fn wasm_pages(self) -> u64 {
281 self.wasm
282 }
283
284 #[must_use]
286 pub const fn stable_pages(self) -> u64 {
287 self.stable
288 }
289}
290
291#[derive(Clone, Copy, Debug, Eq, PartialEq)]
294pub struct MemoryPageSample {
295 start: MemoryPageExtent,
296 end: MemoryPageExtent,
297}
298
299impl MemoryPageSample {
300 const EMPTY: Self = Self {
301 start: MemoryPageExtent::EMPTY,
302 end: MemoryPageExtent::EMPTY,
303 };
304
305 pub(crate) const fn new(start: MemoryPageExtent, end: MemoryPageExtent) -> Self {
306 Self { start, end }
307 }
308
309 #[must_use]
311 pub const fn start(self) -> MemoryPageExtent {
312 self.start
313 }
314
315 #[must_use]
317 pub const fn end(self) -> MemoryPageExtent {
318 self.end
319 }
320
321 #[must_use]
326 pub const fn wasm_growth_pages(self) -> u64 {
327 self.end.wasm.saturating_sub(self.start.wasm)
328 }
329
330 #[must_use]
335 pub const fn stable_growth_pages(self) -> u64 {
336 self.end.stable.saturating_sub(self.start.stable)
337 }
338}
339
340#[derive(Clone, Copy, Debug, Eq, PartialEq)]
346pub struct MemoryPageSummary {
347 samples: u64,
348 latest: MemoryPageSample,
349 maximum_wasm_growth: u64,
350 maximum_stable_growth: u64,
351}
352
353impl MemoryPageSummary {
354 const EMPTY: Self = Self {
355 samples: 0,
356 latest: MemoryPageSample::EMPTY,
357 maximum_wasm_growth: 0,
358 maximum_stable_growth: 0,
359 };
360
361 const fn record(&mut self, sample: MemoryPageSample) {
362 self.samples = self.samples.saturating_add(1);
363 self.latest = sample;
364 self.maximum_wasm_growth = max_u64(self.maximum_wasm_growth, sample.wasm_growth_pages());
365 self.maximum_stable_growth =
366 max_u64(self.maximum_stable_growth, sample.stable_growth_pages());
367 }
368
369 #[must_use]
371 pub const fn samples(self) -> u64 {
372 self.samples
373 }
374
375 #[must_use]
377 pub const fn latest(self) -> Option<MemoryPageSample> {
378 if self.samples == 0 {
379 None
380 } else {
381 Some(self.latest)
382 }
383 }
384
385 #[must_use]
387 pub const fn maximum_wasm_growth_pages(self) -> Option<u64> {
388 if self.samples == 0 {
389 None
390 } else {
391 Some(self.maximum_wasm_growth)
392 }
393 }
394
395 #[must_use]
397 pub const fn maximum_stable_growth_pages(self) -> Option<u64> {
398 if self.samples == 0 {
399 None
400 } else {
401 Some(self.maximum_stable_growth)
402 }
403 }
404}
405
406const fn max_u64(left: u64, right: u64) -> u64 {
407 if left > right { left } else { right }
408}
409
410#[derive(Clone, Copy, Debug, Eq, PartialEq)]
421pub struct TimerPerformance {
422 scheduler_instructions: MeasurementSummary,
423 work_instructions: MeasurementSummary,
424 scheduler_memory_pages: MemoryPageSummary,
425 work_memory_pages: MemoryPageSummary,
426}
427
428impl TimerPerformance {
429 const EMPTY: Self = Self {
430 scheduler_instructions: MeasurementSummary::EMPTY,
431 work_instructions: MeasurementSummary::EMPTY,
432 scheduler_memory_pages: MemoryPageSummary::EMPTY,
433 work_memory_pages: MemoryPageSummary::EMPTY,
434 };
435
436 pub(crate) const fn record_scheduler(&mut self, instructions: u64, memory: MemoryPageSample) {
437 self.scheduler_instructions.record(instructions);
438 self.scheduler_memory_pages.record(memory);
439 }
440
441 pub(crate) const fn record_work(&mut self, instructions: u64, memory: MemoryPageSample) {
442 self.work_instructions.record(instructions);
443 self.work_memory_pages.record(memory);
444 }
445
446 #[must_use]
448 pub const fn scheduler_instructions(self) -> MeasurementSummary {
449 self.scheduler_instructions
450 }
451
452 #[must_use]
454 pub const fn work_instructions(self) -> MeasurementSummary {
455 self.work_instructions
456 }
457
458 #[must_use]
460 pub const fn scheduler_memory_pages(self) -> MemoryPageSummary {
461 self.scheduler_memory_pages
462 }
463
464 #[must_use]
466 pub const fn work_memory_pages(self) -> MemoryPageSummary {
467 self.work_memory_pages
468 }
469}
470
471#[derive(Clone, Copy, Debug, Eq, PartialEq)]
473pub struct TimerObservabilitySnapshot {
474 epoch: TimerEpoch,
475 outcomes: TimerOutcomeSnapshot,
476 counters: TimerCounters,
477 performance: TimerPerformance,
478}
479
480impl TimerObservabilitySnapshot {
481 pub(crate) const fn new(epoch: TimerEpoch) -> Self {
482 Self {
483 epoch,
484 outcomes: TimerOutcomeSnapshot::EMPTY,
485 counters: TimerCounters::EMPTY,
486 performance: TimerPerformance::EMPTY,
487 }
488 }
489
490 pub(crate) const fn record_completion(
491 &mut self,
492 completion: TimerCompletion,
493 completed_at_ns: u64,
494 ) {
495 self.outcomes.record_completion(completion, completed_at_ns);
496 self.counters.record_completion(completion.outcome());
497 }
498
499 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
500 self.outcomes.record_unacknowledged(observed_at_ns);
501 self.counters.record_unacknowledged();
502 }
503
504 pub(crate) const fn counters_mut(&mut self) -> &mut TimerCounters {
505 &mut self.counters
506 }
507
508 pub(crate) const fn record_scheduler_measurements(
509 &mut self,
510 instructions: u64,
511 memory: MemoryPageSample,
512 ) {
513 self.performance.record_scheduler(instructions, memory);
514 }
515
516 pub(crate) const fn record_work_measurements(
517 &mut self,
518 instructions: u64,
519 memory: MemoryPageSample,
520 ) {
521 self.performance.record_work(instructions, memory);
522 }
523
524 #[must_use]
526 pub const fn epoch(self) -> TimerEpoch {
527 self.epoch
528 }
529
530 #[must_use]
532 pub const fn outcomes(self) -> TimerOutcomeSnapshot {
533 self.outcomes
534 }
535
536 #[must_use]
538 pub const fn counters(self) -> TimerCounters {
539 self.counters
540 }
541
542 #[must_use]
544 pub const fn performance(self) -> TimerPerformance {
545 self.performance
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn all_counters_saturate() {
555 let mut counters = TimerCounters {
556 schedule_requests: u64::MAX,
557 wakeups_armed: u64::MAX,
558 work_dispatched: u64::MAX,
559 scheduler_started: u64::MAX,
560 work_started: u64::MAX,
561 work_completed: u64::MAX,
562 succeeded: u64::MAX,
563 no_work: u64::MAX,
564 retryable_failure: u64::MAX,
565 invariant_failure: u64::MAX,
566 cancelled: u64::MAX,
567 stale_wakeups: u64::MAX,
568 stale_work: u64::MAX,
569 coalesced: u64::MAX,
570 unacknowledged: u64::MAX,
571 };
572
573 counters.record_schedule_request();
574 counters.record_wakeup_armed();
575 counters.record_work_dispatched();
576 counters.record_scheduler_started();
577 counters.record_work_started();
578 counters.record_completion(TimerCompletionOutcome::Success);
579 counters.record_cancellation();
580 counters.record_stale_wakeup();
581 counters.record_stale_work();
582 counters.record_coalesced();
583 counters.record_unacknowledged();
584
585 assert_eq!(counters.schedule_requests(), u64::MAX);
586 assert_eq!(counters.wakeups_armed(), u64::MAX);
587 assert_eq!(counters.work_dispatched(), u64::MAX);
588 assert_eq!(counters.work_completed(), u64::MAX);
589 assert_eq!(counters.cancelled(), u64::MAX);
590 assert_eq!(counters.stale_wakeups(), u64::MAX);
591 assert_eq!(counters.stale_work(), u64::MAX);
592 assert_eq!(counters.coalesced(), u64::MAX);
593 assert_eq!(counters.unacknowledged(), u64::MAX);
594 assert!(counters.completion_partition_is_valid());
595 }
596
597 #[test]
598 fn callback_measurements_are_role_specific_bounded_and_saturating() {
599 let mut performance = TimerPerformance::EMPTY;
600 performance.record_scheduler(
601 20,
602 MemoryPageSample::new(MemoryPageExtent::new(1, 2), MemoryPageExtent::new(2, 4)),
603 );
604 performance.record_work(
605 30,
606 MemoryPageSample::new(MemoryPageExtent::new(2, 4), MemoryPageExtent::new(5, 5)),
607 );
608 performance.record_work(
609 10,
610 MemoryPageSample::new(MemoryPageExtent::new(5, 5), MemoryPageExtent::new(6, 9)),
611 );
612
613 assert_eq!(performance.scheduler_instructions().total(), 20);
614 assert_eq!(performance.work_instructions().samples(), 2);
615 assert_eq!(performance.work_instructions().total(), 40);
616 assert_eq!(performance.work_instructions().latest(), Some(10));
617 assert_eq!(performance.work_instructions().maximum(), Some(30));
618
619 let scheduler_memory = performance.scheduler_memory_pages();
620 assert_eq!(
621 scheduler_memory.samples(),
622 performance.scheduler_instructions().samples()
623 );
624 assert_eq!(scheduler_memory.samples(), 1);
625 let scheduler_latest = scheduler_memory
626 .latest()
627 .expect("scheduler sample should exist");
628 assert_eq!(scheduler_latest.start().wasm_pages(), 1);
629 assert_eq!(scheduler_latest.start().stable_pages(), 2);
630 assert_eq!(scheduler_latest.end().wasm_pages(), 2);
631 assert_eq!(scheduler_latest.end().stable_pages(), 4);
632 assert_eq!(scheduler_latest.wasm_growth_pages(), 1);
633 assert_eq!(scheduler_latest.stable_growth_pages(), 2);
634
635 let work_memory = performance.work_memory_pages();
636 assert_eq!(
637 work_memory.samples(),
638 performance.work_instructions().samples()
639 );
640 assert_eq!(work_memory.samples(), 2);
641 let work_latest = work_memory.latest().expect("work sample should exist");
642 assert_eq!(work_latest.wasm_growth_pages(), 1);
643 assert_eq!(work_latest.stable_growth_pages(), 4);
644 assert_eq!(work_memory.maximum_wasm_growth_pages(), Some(3));
645 assert_eq!(work_memory.maximum_stable_growth_pages(), Some(4));
646 }
647
648 #[test]
649 fn memory_sample_count_saturates_while_latest_and_maximum_continue() {
650 let mut summary = MemoryPageSummary {
651 samples: u64::MAX,
652 latest: MemoryPageSample::EMPTY,
653 maximum_wasm_growth: 1,
654 maximum_stable_growth: 1,
655 };
656 let sample =
657 MemoryPageSample::new(MemoryPageExtent::new(10, 20), MemoryPageExtent::new(13, 25));
658
659 summary.record(sample);
660
661 assert_eq!(summary.samples(), u64::MAX);
662 assert_eq!(summary.latest(), Some(sample));
663 assert_eq!(summary.maximum_wasm_growth_pages(), Some(3));
664 assert_eq!(summary.maximum_stable_growth_pages(), Some(5));
665 }
666}