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)]
412pub struct TimerPerformance {
413 scheduler_instructions: MeasurementSummary,
414 work_instructions: MeasurementSummary,
415 scheduler_memory_pages: MemoryPageSummary,
416 work_memory_pages: MemoryPageSummary,
417}
418
419impl TimerPerformance {
420 const EMPTY: Self = Self {
421 scheduler_instructions: MeasurementSummary::EMPTY,
422 work_instructions: MeasurementSummary::EMPTY,
423 scheduler_memory_pages: MemoryPageSummary::EMPTY,
424 work_memory_pages: MemoryPageSummary::EMPTY,
425 };
426
427 pub(crate) const fn record_scheduler(&mut self, instructions: u64, memory: MemoryPageSample) {
428 self.scheduler_instructions.record(instructions);
429 self.scheduler_memory_pages.record(memory);
430 }
431
432 pub(crate) const fn record_work(&mut self, instructions: u64, memory: MemoryPageSample) {
433 self.work_instructions.record(instructions);
434 self.work_memory_pages.record(memory);
435 }
436
437 #[must_use]
439 pub const fn scheduler_instructions(self) -> MeasurementSummary {
440 self.scheduler_instructions
441 }
442
443 #[must_use]
445 pub const fn work_instructions(self) -> MeasurementSummary {
446 self.work_instructions
447 }
448
449 #[must_use]
451 pub const fn scheduler_memory_pages(self) -> MemoryPageSummary {
452 self.scheduler_memory_pages
453 }
454
455 #[must_use]
457 pub const fn work_memory_pages(self) -> MemoryPageSummary {
458 self.work_memory_pages
459 }
460}
461
462#[derive(Clone, Copy, Debug, Eq, PartialEq)]
464pub struct TimerObservabilitySnapshot {
465 epoch: TimerEpoch,
466 outcomes: TimerOutcomeSnapshot,
467 counters: TimerCounters,
468 performance: TimerPerformance,
469}
470
471impl TimerObservabilitySnapshot {
472 pub(crate) const fn new(epoch: TimerEpoch) -> Self {
473 Self {
474 epoch,
475 outcomes: TimerOutcomeSnapshot::EMPTY,
476 counters: TimerCounters::EMPTY,
477 performance: TimerPerformance::EMPTY,
478 }
479 }
480
481 pub(crate) const fn record_completion(
482 &mut self,
483 completion: TimerCompletion,
484 completed_at_ns: u64,
485 ) {
486 self.outcomes.record_completion(completion, completed_at_ns);
487 self.counters.record_completion(completion.outcome());
488 }
489
490 pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
491 self.outcomes.record_unacknowledged(observed_at_ns);
492 self.counters.record_unacknowledged();
493 }
494
495 pub(crate) const fn counters_mut(&mut self) -> &mut TimerCounters {
496 &mut self.counters
497 }
498
499 pub(crate) const fn record_scheduler_measurements(
500 &mut self,
501 instructions: u64,
502 memory: MemoryPageSample,
503 ) {
504 self.performance.record_scheduler(instructions, memory);
505 }
506
507 pub(crate) const fn record_work_measurements(
508 &mut self,
509 instructions: u64,
510 memory: MemoryPageSample,
511 ) {
512 self.performance.record_work(instructions, memory);
513 }
514
515 #[must_use]
517 pub const fn epoch(self) -> TimerEpoch {
518 self.epoch
519 }
520
521 #[must_use]
523 pub const fn outcomes(self) -> TimerOutcomeSnapshot {
524 self.outcomes
525 }
526
527 #[must_use]
529 pub const fn counters(self) -> TimerCounters {
530 self.counters
531 }
532
533 #[must_use]
535 pub const fn performance(self) -> TimerPerformance {
536 self.performance
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543
544 #[test]
545 fn all_counters_saturate() {
546 let mut counters = TimerCounters {
547 schedule_requests: u64::MAX,
548 wakeups_armed: u64::MAX,
549 work_dispatched: u64::MAX,
550 scheduler_started: u64::MAX,
551 work_started: u64::MAX,
552 work_completed: u64::MAX,
553 succeeded: u64::MAX,
554 no_work: u64::MAX,
555 retryable_failure: u64::MAX,
556 invariant_failure: u64::MAX,
557 cancelled: u64::MAX,
558 stale_wakeups: u64::MAX,
559 stale_work: u64::MAX,
560 coalesced: u64::MAX,
561 unacknowledged: u64::MAX,
562 };
563
564 counters.record_schedule_request();
565 counters.record_wakeup_armed();
566 counters.record_work_dispatched();
567 counters.record_scheduler_started();
568 counters.record_work_started();
569 counters.record_completion(TimerCompletionOutcome::Success);
570 counters.record_cancellation();
571 counters.record_stale_wakeup();
572 counters.record_stale_work();
573 counters.record_coalesced();
574 counters.record_unacknowledged();
575
576 assert_eq!(counters.schedule_requests(), u64::MAX);
577 assert_eq!(counters.wakeups_armed(), u64::MAX);
578 assert_eq!(counters.work_dispatched(), u64::MAX);
579 assert_eq!(counters.work_completed(), u64::MAX);
580 assert_eq!(counters.cancelled(), u64::MAX);
581 assert_eq!(counters.stale_wakeups(), u64::MAX);
582 assert_eq!(counters.stale_work(), u64::MAX);
583 assert_eq!(counters.coalesced(), u64::MAX);
584 assert_eq!(counters.unacknowledged(), u64::MAX);
585 assert!(counters.completion_partition_is_valid());
586 }
587
588 #[test]
589 fn callback_measurements_are_role_specific_bounded_and_saturating() {
590 let mut performance = TimerPerformance::EMPTY;
591 performance.record_scheduler(
592 20,
593 MemoryPageSample::new(MemoryPageExtent::new(1, 2), MemoryPageExtent::new(2, 4)),
594 );
595 performance.record_work(
596 30,
597 MemoryPageSample::new(MemoryPageExtent::new(2, 4), MemoryPageExtent::new(5, 5)),
598 );
599 performance.record_work(
600 10,
601 MemoryPageSample::new(MemoryPageExtent::new(5, 5), MemoryPageExtent::new(6, 9)),
602 );
603
604 assert_eq!(performance.scheduler_instructions().total(), 20);
605 assert_eq!(performance.work_instructions().samples(), 2);
606 assert_eq!(performance.work_instructions().total(), 40);
607 assert_eq!(performance.work_instructions().latest(), Some(10));
608 assert_eq!(performance.work_instructions().maximum(), Some(30));
609
610 let scheduler_memory = performance.scheduler_memory_pages();
611 assert_eq!(
612 scheduler_memory.samples(),
613 performance.scheduler_instructions().samples()
614 );
615 assert_eq!(scheduler_memory.samples(), 1);
616 let scheduler_latest = scheduler_memory
617 .latest()
618 .expect("scheduler sample should exist");
619 assert_eq!(scheduler_latest.start().wasm_pages(), 1);
620 assert_eq!(scheduler_latest.start().stable_pages(), 2);
621 assert_eq!(scheduler_latest.end().wasm_pages(), 2);
622 assert_eq!(scheduler_latest.end().stable_pages(), 4);
623 assert_eq!(scheduler_latest.wasm_growth_pages(), 1);
624 assert_eq!(scheduler_latest.stable_growth_pages(), 2);
625
626 let work_memory = performance.work_memory_pages();
627 assert_eq!(
628 work_memory.samples(),
629 performance.work_instructions().samples()
630 );
631 assert_eq!(work_memory.samples(), 2);
632 let work_latest = work_memory.latest().expect("work sample should exist");
633 assert_eq!(work_latest.wasm_growth_pages(), 1);
634 assert_eq!(work_latest.stable_growth_pages(), 4);
635 assert_eq!(work_memory.maximum_wasm_growth_pages(), Some(3));
636 assert_eq!(work_memory.maximum_stable_growth_pages(), Some(4));
637 }
638
639 #[test]
640 fn memory_sample_count_saturates_while_latest_and_maximum_continue() {
641 let mut summary = MemoryPageSummary {
642 samples: u64::MAX,
643 latest: MemoryPageSample::EMPTY,
644 maximum_wasm_growth: 1,
645 maximum_stable_growth: 1,
646 };
647 let sample =
648 MemoryPageSample::new(MemoryPageExtent::new(10, 20), MemoryPageExtent::new(13, 25));
649
650 summary.record(sample);
651
652 assert_eq!(summary.samples(), u64::MAX);
653 assert_eq!(summary.latest(), Some(sample));
654 assert_eq!(summary.maximum_wasm_growth_pages(), Some(3));
655 assert_eq!(summary.maximum_stable_growth_pages(), Some(5));
656 }
657}