use super::{TimerCompletion, TimerCompletionOutcome, TimerEpoch, TimerOutcomeSnapshot};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct TimerCounters {
requested: u64,
armed: u64,
started: u64,
completed: u64,
succeeded: u64,
no_work: u64,
retryable_failure: u64,
invariant_failure: u64,
cancelled: u64,
stale: u64,
coalesced: u64,
interrupted: u64,
}
impl TimerCounters {
pub const fn record_request(&mut self) {
self.requested = self.requested.saturating_add(1);
}
pub const fn record_arm(&mut self) {
self.armed = self.armed.saturating_add(1);
}
pub const fn record_start(&mut self) {
self.started = self.started.saturating_add(1);
}
pub const fn record_completion(&mut self, outcome: TimerCompletionOutcome) {
self.completed = self.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 const fn record_cancellation(&mut self) {
self.cancelled = self.cancelled.saturating_add(1);
}
pub const fn record_stale(&mut self) {
self.stale = self.stale.saturating_add(1);
}
pub const fn record_coalesced(&mut self) {
self.coalesced = self.coalesced.saturating_add(1);
}
pub const fn record_interruption(&mut self) {
self.interrupted = self.interrupted.saturating_add(1);
}
#[must_use]
pub const fn requested(self) -> u64 {
self.requested
}
#[must_use]
pub const fn armed(self) -> u64 {
self.armed
}
#[must_use]
pub const fn started(self) -> u64 {
self.started
}
#[must_use]
pub const fn completed(self) -> u64 {
self.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(self) -> u64 {
self.stale
}
#[must_use]
pub const fn coalesced(self) -> u64 {
self.coalesced
}
#[must_use]
pub const fn interrupted(self) -> u64 {
self.interrupted
}
#[must_use]
pub const fn completion_partition_is_valid(self) -> bool {
self.completed
== self
.succeeded
.saturating_add(self.no_work)
.saturating_add(self.retryable_failure)
.saturating_add(self.invariant_failure)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimerMeasurement {
pub instructions: u64,
pub elapsed_ns: u64,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct MeasurementSummary {
samples: u64,
total: u64,
latest: Option<u64>,
maximum: Option<u64>,
}
impl MeasurementSummary {
pub 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 {
instructions: MeasurementSummary,
elapsed_ns: MeasurementSummary,
}
impl TimerPerformance {
pub const fn record(&mut self, measurement: TimerMeasurement) {
self.instructions.record(measurement.instructions);
self.elapsed_ns.record(measurement.elapsed_ns);
}
#[must_use]
pub const fn instructions(self) -> MeasurementSummary {
self.instructions
}
#[must_use]
pub const fn elapsed_ns(self) -> MeasurementSummary {
self.elapsed_ns
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimerObservabilitySnapshot {
epoch: TimerEpoch,
outcomes: TimerOutcomeSnapshot,
counters: TimerCounters,
performance: TimerPerformance,
}
impl TimerObservabilitySnapshot {
#[must_use]
pub fn new(epoch: TimerEpoch) -> Self {
Self {
epoch,
outcomes: TimerOutcomeSnapshot::default(),
counters: TimerCounters::default(),
performance: TimerPerformance::default(),
}
}
pub fn begin_epoch(&mut self, epoch: TimerEpoch) {
*self = Self::new(epoch);
}
pub const fn record_completion(
&mut self,
completion: TimerCompletion,
completed_at_ns: u64,
measurement: Option<TimerMeasurement>,
) {
self.outcomes.record_completion(completion, completed_at_ns);
self.counters.record_completion(completion.outcome);
if let Some(measurement) = measurement {
self.performance.record(measurement);
}
}
pub const fn record_interruption(&mut self, observed_at_ns: u64) {
self.outcomes.record_interruption(observed_at_ns);
self.counters.record_interruption();
}
pub const fn record_request(&mut self) {
self.counters.record_request();
}
pub const fn record_arm(&mut self) {
self.counters.record_arm();
}
pub const fn record_start(&mut self) {
self.counters.record_start();
}
pub const fn record_cancellation(&mut self) {
self.counters.record_cancellation();
}
pub const fn record_stale(&mut self) {
self.counters.record_stale();
}
pub const fn record_coalesced(&mut self) {
self.counters.record_coalesced();
}
#[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 {
requested: u64::MAX,
armed: u64::MAX,
started: u64::MAX,
completed: u64::MAX,
succeeded: u64::MAX,
no_work: u64::MAX,
retryable_failure: u64::MAX,
invariant_failure: u64::MAX,
cancelled: u64::MAX,
stale: u64::MAX,
coalesced: u64::MAX,
interrupted: u64::MAX,
};
counters.record_request();
counters.record_arm();
counters.record_start();
counters.record_completion(TimerCompletionOutcome::Success);
counters.record_cancellation();
counters.record_stale();
counters.record_coalesced();
counters.record_interruption();
assert_eq!(counters.requested(), u64::MAX);
assert_eq!(counters.armed(), u64::MAX);
assert_eq!(counters.started(), u64::MAX);
assert_eq!(counters.completed(), u64::MAX);
assert_eq!(counters.succeeded(), u64::MAX);
assert_eq!(counters.cancelled(), u64::MAX);
assert_eq!(counters.stale(), u64::MAX);
assert_eq!(counters.coalesced(), u64::MAX);
assert_eq!(counters.interrupted(), u64::MAX);
assert!(counters.completion_partition_is_valid());
}
#[test]
fn measurement_count_and_total_saturate_while_latest_and_maximum_advance() {
let mut summary = MeasurementSummary {
samples: u64::MAX,
total: u64::MAX,
latest: Some(10),
maximum: Some(20),
};
summary.record(30);
assert_eq!(summary.samples(), u64::MAX);
assert_eq!(summary.total(), u64::MAX);
assert_eq!(summary.latest(), Some(30));
assert_eq!(summary.maximum(), Some(30));
}
}