Skip to main content

ic_timers/snapshot/
metrics.rs

1//! Saturating epoch-local counters and instruction aggregates.
2
3use super::{TimerCompletion, TimerCompletionOutcome, TimerEpoch, TimerOutcomeSnapshot};
4
5/// Epoch-local timer event counters.
6///
7/// Fields are private so mutation preserves saturation and the completion
8/// partition. The registry is the sole writer.
9#[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    /// Return validated activation and reconciliation requests.
106    #[must_use]
107    pub const fn schedule_requests(self) -> u64 {
108        self.schedule_requests
109    }
110
111    /// Return ordinary or scheduler provider one-shots armed.
112    #[must_use]
113    pub const fn wakeups_armed(self) -> u64 {
114        self.wakeups_armed
115    }
116
117    /// Return immediate watchdog work one-shots dispatched.
118    #[must_use]
119    pub const fn work_dispatched(self) -> u64 {
120        self.work_dispatched
121    }
122
123    /// Return accepted watchdog scheduler callbacks.
124    #[must_use]
125    pub const fn scheduler_started(self) -> u64 {
126        self.scheduler_started
127    }
128
129    /// Return accepted consumer-work callbacks.
130    #[must_use]
131    pub const fn work_started(self) -> u64 {
132        self.work_started
133    }
134
135    /// Return consumer work whose completion accounting committed.
136    #[must_use]
137    pub const fn work_completed(self) -> u64 {
138        self.work_completed
139    }
140
141    /// Return successful-work completions.
142    #[must_use]
143    pub const fn succeeded(self) -> u64 {
144        self.succeeded
145    }
146
147    /// Return valid no-work completions.
148    #[must_use]
149    pub const fn no_work(self) -> u64 {
150        self.no_work
151    }
152
153    /// Return retryable expected-failure completions.
154    #[must_use]
155    pub const fn retryable_failure(self) -> u64 {
156        self.retryable_failure
157    }
158
159    /// Return invariant or terminal-failure completions.
160    #[must_use]
161    pub const fn invariant_failure(self) -> u64 {
162        self.invariant_failure
163    }
164
165    /// Return logical cancellations that changed authoritative state.
166    #[must_use]
167    pub const fn cancelled(self) -> u64 {
168        self.cancelled
169    }
170
171    /// Return rejected ordinary or scheduler callback generations.
172    #[must_use]
173    pub const fn stale_wakeups(self) -> u64 {
174        self.stale_wakeups
175    }
176
177    /// Return rejected watchdog work generations.
178    #[must_use]
179    pub const fn stale_work(self) -> u64 {
180        self.stale_work
181    }
182
183    /// Return scheduling demand satisfied without another logical arm.
184    #[must_use]
185    pub const fn coalesced(self) -> u64 {
186        self.coalesced
187    }
188
189    /// Return committed watchdog dispatches retired without completion.
190    #[must_use]
191    pub const fn unacknowledged(self) -> u64 {
192        self.unacknowledged
193    }
194
195    /// Check the owner-local completion partition invariant.
196    #[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/// Saturating aggregate for one instruction measurement role.
209#[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    /// Return the number of completed samples.
236    #[must_use]
237    pub const fn samples(self) -> u64 {
238        self.samples
239    }
240
241    /// Return the saturating sum of all samples.
242    #[must_use]
243    pub const fn total(self) -> u64 {
244        self.total
245    }
246
247    /// Return the latest sample, if one exists.
248    #[must_use]
249    pub const fn latest(self) -> Option<u64> {
250        self.latest
251    }
252
253    /// Return the largest sample, if one exists.
254    #[must_use]
255    pub const fn maximum(self) -> Option<u64> {
256        self.maximum
257    }
258}
259
260/// Completed instruction aggregates split by callback role.
261#[derive(Clone, Copy, Debug, Eq, PartialEq)]
262pub struct TimerPerformance {
263    scheduler_instructions: MeasurementSummary,
264    work_instructions: MeasurementSummary,
265}
266
267impl TimerPerformance {
268    const EMPTY: Self = Self {
269        scheduler_instructions: MeasurementSummary::EMPTY,
270        work_instructions: MeasurementSummary::EMPTY,
271    };
272
273    pub(crate) const fn record_scheduler(&mut self, instructions: u64) {
274        self.scheduler_instructions.record(instructions);
275    }
276
277    pub(crate) const fn record_work(&mut self, instructions: u64) {
278        self.work_instructions.record(instructions);
279    }
280
281    /// Return normally completed scheduler instruction measurements.
282    #[must_use]
283    pub const fn scheduler_instructions(self) -> MeasurementSummary {
284        self.scheduler_instructions
285    }
286
287    /// Return normally completed consumer-work instruction measurements.
288    #[must_use]
289    pub const fn work_instructions(self) -> MeasurementSummary {
290        self.work_instructions
291    }
292}
293
294/// Epoch-scoped outcomes, counters, and performance for one timer.
295#[derive(Clone, Copy, Debug, Eq, PartialEq)]
296pub struct TimerObservabilitySnapshot {
297    epoch: TimerEpoch,
298    outcomes: TimerOutcomeSnapshot,
299    counters: TimerCounters,
300    performance: TimerPerformance,
301}
302
303impl TimerObservabilitySnapshot {
304    pub(crate) const fn new(epoch: TimerEpoch) -> Self {
305        Self {
306            epoch,
307            outcomes: TimerOutcomeSnapshot::EMPTY,
308            counters: TimerCounters::EMPTY,
309            performance: TimerPerformance::EMPTY,
310        }
311    }
312
313    pub(crate) const fn record_completion(
314        &mut self,
315        completion: TimerCompletion,
316        completed_at_ns: u64,
317    ) {
318        self.outcomes.record_completion(completion, completed_at_ns);
319        self.counters.record_completion(completion.outcome());
320    }
321
322    pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
323        self.outcomes.record_unacknowledged(observed_at_ns);
324        self.counters.record_unacknowledged();
325    }
326
327    pub(crate) const fn counters_mut(&mut self) -> &mut TimerCounters {
328        &mut self.counters
329    }
330
331    pub(crate) const fn record_scheduler_instructions(&mut self, instructions: u64) {
332        self.performance.record_scheduler(instructions);
333    }
334
335    pub(crate) const fn record_work_instructions(&mut self, instructions: u64) {
336        self.performance.record_work(instructions);
337    }
338
339    /// Return the observation epoch.
340    #[must_use]
341    pub const fn epoch(self) -> TimerEpoch {
342        self.epoch
343    }
344
345    /// Return latest outcomes and functional failure state.
346    #[must_use]
347    pub const fn outcomes(self) -> TimerOutcomeSnapshot {
348        self.outcomes
349    }
350
351    /// Return epoch-local event counters.
352    #[must_use]
353    pub const fn counters(self) -> TimerCounters {
354        self.counters
355    }
356
357    /// Return completed instruction aggregates.
358    #[must_use]
359    pub const fn performance(self) -> TimerPerformance {
360        self.performance
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367
368    #[test]
369    fn all_counters_saturate() {
370        let mut counters = TimerCounters {
371            schedule_requests: u64::MAX,
372            wakeups_armed: u64::MAX,
373            work_dispatched: u64::MAX,
374            scheduler_started: u64::MAX,
375            work_started: u64::MAX,
376            work_completed: u64::MAX,
377            succeeded: u64::MAX,
378            no_work: u64::MAX,
379            retryable_failure: u64::MAX,
380            invariant_failure: u64::MAX,
381            cancelled: u64::MAX,
382            stale_wakeups: u64::MAX,
383            stale_work: u64::MAX,
384            coalesced: u64::MAX,
385            unacknowledged: u64::MAX,
386        };
387
388        counters.record_schedule_request();
389        counters.record_wakeup_armed();
390        counters.record_work_dispatched();
391        counters.record_scheduler_started();
392        counters.record_work_started();
393        counters.record_completion(TimerCompletionOutcome::Success);
394        counters.record_cancellation();
395        counters.record_stale_wakeup();
396        counters.record_stale_work();
397        counters.record_coalesced();
398        counters.record_unacknowledged();
399
400        assert_eq!(counters.schedule_requests(), u64::MAX);
401        assert_eq!(counters.wakeups_armed(), u64::MAX);
402        assert_eq!(counters.work_dispatched(), u64::MAX);
403        assert_eq!(counters.work_completed(), u64::MAX);
404        assert_eq!(counters.cancelled(), u64::MAX);
405        assert_eq!(counters.stale_wakeups(), u64::MAX);
406        assert_eq!(counters.stale_work(), u64::MAX);
407        assert_eq!(counters.coalesced(), u64::MAX);
408        assert_eq!(counters.unacknowledged(), u64::MAX);
409        assert!(counters.completion_partition_is_valid());
410    }
411
412    #[test]
413    fn instruction_roles_are_separate_and_saturating() {
414        let mut performance = TimerPerformance::EMPTY;
415        performance.record_scheduler(20);
416        performance.record_work(30);
417        performance.record_work(10);
418
419        assert_eq!(performance.scheduler_instructions().total(), 20);
420        assert_eq!(performance.work_instructions().samples(), 2);
421        assert_eq!(performance.work_instructions().total(), 40);
422        assert_eq!(performance.work_instructions().latest(), Some(10));
423        assert_eq!(performance.work_instructions().maximum(), Some(30));
424    }
425}