use {
solana_metrics::datapoint_info,
std::time::{Duration, Instant},
};
const SUBMIT_INTERVAL: Duration = Duration::from_secs(60);
#[derive(Debug)]
pub(super) struct StatsManager {
stats: Stats,
previous_submit: Instant,
}
impl StatsManager {
#[must_use]
pub(super) fn new() -> Self {
Self {
stats: Stats::default(),
previous_submit: Instant::now(),
}
}
pub(super) fn record_and_maybe_submit(&mut self, runtime: Duration) {
self.stats.record(runtime);
self.maybe_submit();
}
fn maybe_submit(&mut self) {
let duration_since_previous_submit = Instant::now() - self.previous_submit;
if duration_since_previous_submit < SUBMIT_INTERVAL {
return;
}
datapoint_info!(
"accounts_background_service",
(
"duration_since_previous_submit-ms",
duration_since_previous_submit.as_millis() as i64,
i64
),
("num_iterations", self.stats.num_iterations as i64, i64),
(
"cumulative_runtime-ms",
self.stats.cumulative_runtime.as_millis() as i64,
i64
),
(
"mean_runtime-ms",
self.stats.mean_runtime().as_millis() as i64,
i64
),
(
"min_runtime-ms",
self.stats.min_runtime.as_millis() as i64,
i64
),
(
"max_runtime-ms",
self.stats.max_runtime.as_millis() as i64,
i64
),
);
*self = Self::new();
}
}
#[derive(Debug)]
struct Stats {
num_iterations: usize,
cumulative_runtime: Duration,
min_runtime: Duration,
max_runtime: Duration,
}
impl Stats {
fn record(&mut self, runtime: Duration) {
self.num_iterations += 1;
self.cumulative_runtime += runtime;
self.min_runtime = self.min_runtime.min(runtime);
self.max_runtime = self.max_runtime.max(runtime);
}
fn mean_runtime(&self) -> Duration {
debug_assert!(self.num_iterations > 0);
debug_assert!(self.num_iterations <= u32::MAX as usize);
self.cumulative_runtime / self.num_iterations as u32
}
}
impl Default for Stats {
#[must_use]
fn default() -> Self {
Self {
num_iterations: 0,
cumulative_runtime: Duration::ZERO,
min_runtime: Duration::MAX,
max_runtime: Duration::ZERO,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stats_record() {
let mut stats = Stats::default();
let runtime1 = Duration::from_secs(44);
stats.record(runtime1);
assert_eq!(stats.num_iterations, 1);
assert_eq!(stats.cumulative_runtime, runtime1);
assert_eq!(stats.min_runtime, runtime1);
assert_eq!(stats.max_runtime, runtime1);
let runtime2 = Duration::from_secs(99);
stats.record(runtime2);
assert_eq!(stats.num_iterations, 2);
assert_eq!(stats.cumulative_runtime, runtime1 + runtime2);
assert_eq!(stats.min_runtime, runtime1);
assert_eq!(stats.max_runtime, runtime2);
let runtime3 = Duration::from_secs(11);
stats.record(runtime3);
assert_eq!(stats.num_iterations, 3);
assert_eq!(stats.cumulative_runtime, runtime1 + runtime2 + runtime3);
assert_eq!(stats.min_runtime, runtime3);
assert_eq!(stats.max_runtime, runtime2);
}
#[test]
fn test_stats_mean_runtime() {
let mut stats = Stats::default();
stats.record(Duration::from_secs(1));
stats.record(Duration::from_secs(3));
stats.record(Duration::from_secs(5));
stats.record(Duration::from_secs(7));
assert_eq!(stats.mean_runtime().as_secs(), (1 + 3 + 5 + 7) / 4);
}
#[test]
#[should_panic]
fn test_stats_mean_runtime_panic_zero_iterations() {
let stats = Stats::default();
let _ = stats.mean_runtime();
}
#[test]
#[should_panic]
fn test_stats_mean_runtime_panic_too_many_iterations() {
let num_iterations = u32::MAX as usize + 1;
let stats = Stats {
num_iterations,
..Stats::default()
};
let _ = stats.mean_runtime();
}
}