use super::{TimerCompletion, TimerCompletionOutcome, TimerEpoch, TimerOutcomeSnapshot};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct TimerCounters {
schedule_requests: u64,
wakeups_armed: u64,
work_dispatched: u64,
scheduler_started: u64,
work_started: u64,
work_completed: u64,
succeeded: u64,
no_work: u64,
retryable_failure: u64,
invariant_failure: u64,
cancelled: u64,
stale_wakeups: u64,
stale_work: u64,
coalesced: u64,
unacknowledged: u64,
}
impl TimerCounters {
pub(crate) const fn record_schedule_request(&mut self) {
self.schedule_requests = self.schedule_requests.saturating_add(1);
}
pub(crate) const fn record_wakeup_armed(&mut self) {
self.wakeups_armed = self.wakeups_armed.saturating_add(1);
}
pub(crate) const fn record_work_dispatched(&mut self) {
self.work_dispatched = self.work_dispatched.saturating_add(1);
}
pub(crate) const fn record_scheduler_started(&mut self) {
self.scheduler_started = self.scheduler_started.saturating_add(1);
}
pub(crate) const fn record_work_started(&mut self) {
self.work_started = self.work_started.saturating_add(1);
}
pub(crate) const fn record_completion(&mut self, outcome: TimerCompletionOutcome) {
self.work_completed = self.work_completed.saturating_add(1);
match outcome {
TimerCompletionOutcome::Success => {
self.succeeded = self.succeeded.saturating_add(1);
}
TimerCompletionOutcome::NoWork => {
self.no_work = self.no_work.saturating_add(1);
}
TimerCompletionOutcome::RetryableFailure => {
self.retryable_failure = self.retryable_failure.saturating_add(1);
}
TimerCompletionOutcome::InvariantFailure => {
self.invariant_failure = self.invariant_failure.saturating_add(1);
}
}
}
pub(crate) const fn record_cancellation(&mut self) {
self.cancelled = self.cancelled.saturating_add(1);
}
pub(crate) const fn record_stale_wakeup(&mut self) {
self.stale_wakeups = self.stale_wakeups.saturating_add(1);
}
pub(crate) const fn record_stale_work(&mut self) {
self.stale_work = self.stale_work.saturating_add(1);
}
pub(crate) const fn record_coalesced(&mut self) {
self.coalesced = self.coalesced.saturating_add(1);
}
pub(crate) const fn record_unacknowledged(&mut self) {
self.unacknowledged = self.unacknowledged.saturating_add(1);
}
#[must_use]
pub const fn schedule_requests(self) -> u64 {
self.schedule_requests
}
#[must_use]
pub const fn wakeups_armed(self) -> u64 {
self.wakeups_armed
}
#[must_use]
pub const fn work_dispatched(self) -> u64 {
self.work_dispatched
}
#[must_use]
pub const fn provider_arms(self) -> u64 {
self.wakeups_armed.saturating_add(self.work_dispatched)
}
#[must_use]
pub const fn scheduler_started(self) -> u64 {
self.scheduler_started
}
#[must_use]
pub const fn work_started(self) -> u64 {
self.work_started
}
#[must_use]
pub const fn work_completed(self) -> u64 {
self.work_completed
}
#[must_use]
pub const fn succeeded(self) -> u64 {
self.succeeded
}
#[must_use]
pub const fn no_work(self) -> u64 {
self.no_work
}
#[must_use]
pub const fn retryable_failure(self) -> u64 {
self.retryable_failure
}
#[must_use]
pub const fn invariant_failure(self) -> u64 {
self.invariant_failure
}
#[must_use]
pub const fn cancelled(self) -> u64 {
self.cancelled
}
#[must_use]
pub const fn stale_wakeups(self) -> u64 {
self.stale_wakeups
}
#[must_use]
pub const fn stale_work(self) -> u64 {
self.stale_work
}
#[must_use]
pub const fn coalesced(self) -> u64 {
self.coalesced
}
#[must_use]
pub const fn unacknowledged(self) -> u64 {
self.unacknowledged
}
#[must_use]
pub const fn completion_partition_is_valid(self) -> bool {
self.work_completed
== self
.succeeded
.saturating_add(self.no_work)
.saturating_add(self.retryable_failure)
.saturating_add(self.invariant_failure)
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MeasurementSummary {
samples: u64,
total: u64,
latest: Option<u64>,
maximum: Option<u64>,
}
impl MeasurementSummary {
pub(crate) const fn record(&mut self, value: u64) {
self.samples = self.samples.saturating_add(1);
self.total = self.total.saturating_add(value);
self.latest = Some(value);
self.maximum = Some(match self.maximum {
Some(current) if current > value => current,
Some(_) | None => value,
});
}
#[must_use]
pub const fn samples(self) -> u64 {
self.samples
}
#[must_use]
pub const fn total(self) -> u64 {
self.total
}
#[must_use]
pub const fn latest(self) -> Option<u64> {
self.latest
}
#[must_use]
pub const fn maximum(self) -> Option<u64> {
self.maximum
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct TimerPerformance {
scheduler_instructions: MeasurementSummary,
work_instructions: MeasurementSummary,
}
impl TimerPerformance {
pub(crate) const fn record_scheduler(&mut self, instructions: u64) {
self.scheduler_instructions.record(instructions);
}
pub(crate) const fn record_work(&mut self, instructions: u64) {
self.work_instructions.record(instructions);
}
#[must_use]
pub const fn scheduler_instructions(self) -> MeasurementSummary {
self.scheduler_instructions
}
#[must_use]
pub const fn work_instructions(self) -> MeasurementSummary {
self.work_instructions
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimerObservabilitySnapshot {
epoch: TimerEpoch,
outcomes: TimerOutcomeSnapshot,
counters: TimerCounters,
performance: TimerPerformance,
}
impl TimerObservabilitySnapshot {
pub(crate) const fn new(epoch: TimerEpoch) -> Self {
Self {
epoch,
outcomes: TimerOutcomeSnapshot::new(),
counters: TimerCounters {
schedule_requests: 0,
wakeups_armed: 0,
work_dispatched: 0,
scheduler_started: 0,
work_started: 0,
work_completed: 0,
succeeded: 0,
no_work: 0,
retryable_failure: 0,
invariant_failure: 0,
cancelled: 0,
stale_wakeups: 0,
stale_work: 0,
coalesced: 0,
unacknowledged: 0,
},
performance: TimerPerformance {
scheduler_instructions: MeasurementSummary {
samples: 0,
total: 0,
latest: None,
maximum: None,
},
work_instructions: MeasurementSummary {
samples: 0,
total: 0,
latest: None,
maximum: None,
},
},
}
}
pub(crate) const fn record_completion(
&mut self,
completion: TimerCompletion,
completed_at_ns: u64,
) {
self.outcomes.record_completion(completion, completed_at_ns);
self.counters.record_completion(completion.outcome());
}
pub(crate) const fn record_unacknowledged(&mut self, observed_at_ns: u64) {
self.outcomes.record_unacknowledged(observed_at_ns);
self.counters.record_unacknowledged();
}
pub(crate) const fn counters_mut(&mut self) -> &mut TimerCounters {
&mut self.counters
}
pub(crate) const fn record_scheduler_instructions(&mut self, instructions: u64) {
self.performance.record_scheduler(instructions);
}
pub(crate) const fn record_work_instructions(&mut self, instructions: u64) {
self.performance.record_work(instructions);
}
#[must_use]
pub const fn epoch(self) -> TimerEpoch {
self.epoch
}
#[must_use]
pub const fn outcomes(self) -> TimerOutcomeSnapshot {
self.outcomes
}
#[must_use]
pub const fn counters(self) -> TimerCounters {
self.counters
}
#[must_use]
pub const fn performance(self) -> TimerPerformance {
self.performance
}
#[must_use]
pub const fn consecutive_expected_failures(self) -> u64 {
self.outcomes.consecutive_expected_failures()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_counters_saturate() {
let mut counters = TimerCounters {
schedule_requests: u64::MAX,
wakeups_armed: u64::MAX,
work_dispatched: u64::MAX,
scheduler_started: u64::MAX,
work_started: u64::MAX,
work_completed: u64::MAX,
succeeded: u64::MAX,
no_work: u64::MAX,
retryable_failure: u64::MAX,
invariant_failure: u64::MAX,
cancelled: u64::MAX,
stale_wakeups: u64::MAX,
stale_work: u64::MAX,
coalesced: u64::MAX,
unacknowledged: u64::MAX,
};
counters.record_schedule_request();
counters.record_wakeup_armed();
counters.record_work_dispatched();
counters.record_scheduler_started();
counters.record_work_started();
counters.record_completion(TimerCompletionOutcome::Success);
counters.record_cancellation();
counters.record_stale_wakeup();
counters.record_stale_work();
counters.record_coalesced();
counters.record_unacknowledged();
assert_eq!(counters.schedule_requests(), u64::MAX);
assert_eq!(counters.provider_arms(), u64::MAX);
assert_eq!(counters.work_completed(), u64::MAX);
assert_eq!(counters.cancelled(), u64::MAX);
assert_eq!(counters.stale_wakeups(), u64::MAX);
assert_eq!(counters.stale_work(), u64::MAX);
assert_eq!(counters.coalesced(), u64::MAX);
assert_eq!(counters.unacknowledged(), u64::MAX);
assert!(counters.completion_partition_is_valid());
}
#[test]
fn instruction_roles_are_separate_and_saturating() {
let mut performance = TimerPerformance::default();
performance.record_scheduler(20);
performance.record_work(30);
performance.record_work(10);
assert_eq!(performance.scheduler_instructions().total(), 20);
assert_eq!(performance.work_instructions().samples(), 2);
assert_eq!(performance.work_instructions().total(), 40);
assert_eq!(performance.work_instructions().latest(), Some(10));
assert_eq!(performance.work_instructions().maximum(), Some(30));
}
}