mmtk/util/statistics/counter/
latency_sampler.rs

1use super::*;
2use crate::util::statistics::stats::SharedStats;
3use std::sync::Arc;
4
5/// A [`Counter`] that records one latency sample per event (e.g. one per GC pause, for things
6/// like time-to-yield or pause time) and reports it as `p50`/`p9999` columns instead of a raw
7/// per-phase total.
8///
9/// This is a thin wrapper around an [`EventCounter`]: it reuses the inner counter's
10/// `start`/`stop`/`phase_change` machinery unchanged to accumulate one sample per pause into the
11/// per-phase array. It only overrides how the counter is
12/// named and printed, bending two parts of the [`Counter`] contract to do so:
13///
14/// - [`Counter::merge_phases`] always returns `true`.
15/// - [`Counter::name`] returns a compound, tab-separated pair of column names (e.g.
16///   `"pause-time.p50\tpause-time.p9999"`), so the single `print!("{}\t", c.name())` call site
17///   in [`crate::util::statistics::stats::Stats::print_column_names`] prints both headers.
18/// - [`Counter::print_total`] prints `"{p50}\t{p9999}"` (two tab-separated values, ignoring the
19///   `other` argument), so the single `c.print_total(None)` call site in
20///   [`crate::util::statistics::stats::Stats::print_stats`] prints both values.
21///
22/// This trick only works because `Counter::name()` has a single caller (`Stats`'s own printing
23/// code) anywhere in the codebase; if that changes, this would need revisiting.
24pub struct LatencySampler {
25    inner: EventCounter,
26    /// A compound `"{name}.p50\t{name}.p9999"` string, returned by `name()`.
27    display_name: String,
28}
29
30impl LatencySampler {
31    pub fn new(name: &str, stats: Arc<SharedStats>, implicitly_start: bool) -> Self {
32        LatencySampler {
33            inner: EventCounter::new(name.to_string(), stats, implicitly_start, false),
34            display_name: format!("{name}.p50\t{name}.p9999"),
35        }
36    }
37
38    /// Record one latency sample (e.g. a duration in nanoseconds).
39    pub fn record(&mut self, value: u64) {
40        self.inner.inc_by(value);
41    }
42
43    /// The recorded samples, one per pause. Every sample is recorded during the STW phase (see
44    /// `record`), so only the odd-indexed phase counts hold real values; the even-indexed
45    /// (mutator-phase) ones are always 0 and are skipped here.
46    fn samples(&self) -> Vec<u64> {
47        self.inner
48            .count
49            .iter()
50            .skip(1)
51            .step_by(2)
52            .copied()
53            .collect()
54    }
55}
56
57impl Counter for LatencySampler {
58    fn start(&mut self) {
59        self.inner.start();
60    }
61
62    fn stop(&mut self) {
63        self.inner.stop();
64    }
65
66    fn phase_change(&mut self, old_phase: usize) {
67        self.inner.phase_change(old_phase);
68    }
69
70    fn print_count(&self, phase: usize) {
71        self.inner.print_count(phase);
72    }
73
74    fn get_total(&self, other: Option<bool>) -> u64 {
75        self.inner.get_total(other)
76    }
77
78    fn print_total(&self, _other: Option<bool>) {
79        let mut samples = self.samples();
80        if samples.is_empty() {
81            print!("0\t0");
82            return;
83        }
84        // Exact percentiles, computed by sorting all samples and using the nearest-rank method.
85        // Note that with fewer than 10,000 samples, p9999 is guaranteed to just return the
86        // maximum.
87        samples.sort_unstable();
88        let percentile = |p: f64| {
89            let rank = ((p / 100.0) * samples.len() as f64).ceil() as usize;
90            samples[rank.clamp(1, samples.len()) - 1]
91        };
92        let p50_ns = percentile(50.0);
93        let p9999_ns = percentile(99.99);
94        print!("{:.2}\t{:.2}", p50_ns as f64 / 1e6, p9999_ns as f64 / 1e6);
95    }
96
97    fn print_min(&self, other: bool) {
98        self.inner.print_min(other);
99    }
100
101    fn print_max(&self, other: bool) {
102        self.inner.print_max(other);
103    }
104
105    fn print_last(&self) {
106        self.inner.print_last();
107    }
108
109    fn merge_phases(&self) -> bool {
110        true
111    }
112
113    fn implicitly_start(&self) -> bool {
114        self.inner.implicitly_start()
115    }
116
117    fn name(&self) -> &String {
118        &self.display_name
119    }
120}