Skip to main content

ic_backup/ops/persistence/download_journal/metrics/
mod.rs

1//! Caller-owned host diagnostics using shared arithmetic; no retained authority.
2
3use super::DownloadJournalGuard;
4/// Canonical shared arithmetic for local diagnostic summaries and distributions.
5///
6/// Re-exported so callers can name returned summaries without selecting a separate
7/// Metrics dependency. This is the shared type, with no local wrapper or arithmetic.
8pub use ic_metrics::{MeasurementHistogram, MeasurementSummary};
9use std::{
10    sync::PoisonError,
11    time::{Duration, Instant},
12};
13
14const EMPTY_CHUNK_HISTOGRAM: MeasurementHistogram<4> =
15    match MeasurementHistogram::new([0, 32 * 1024, 256 * 1024, 1024 * 1024]) {
16        Ok(histogram) => histogram,
17        Err(_) => panic!("prepared chunk bounds must be strictly increasing"),
18    };
19
20/// Read-only diagnostic snapshot for one opened download-journal guard.
21///
22/// Successful and failed returned calls have separate duration summaries in
23/// nanoseconds. Verification includes internal upload-preparation checks; preparation
24/// durations include those checks and must not be summed with them as exclusive work.
25/// Prepared bytes count successful data payloads only, including empty known chunks;
26/// repeated preparation records another sample, not unique or transferred bytes.
27///
28/// Sampling starts empty on create/open and is never persisted or serialized
29/// or reconstructed from journal evidence. Counts/totals saturate independently;
30/// `u64::MAX` is unavailable for exact interval arithmetic. These values establish
31/// no IC cost, complete transfer, spending, receipt, freshness or outcome authority.
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct IcSnapshotLocalMetrics {
34    verification_success_ns: MeasurementSummary,
35    verification_failure_ns: MeasurementSummary,
36    upload_metadata_success_ns: MeasurementSummary,
37    upload_metadata_failure_ns: MeasurementSummary,
38    upload_data_success_ns: MeasurementSummary,
39    upload_data_failure_ns: MeasurementSummary,
40    prepared_chunk_bytes: MeasurementHistogram<4>,
41}
42
43impl Default for IcSnapshotLocalMetrics {
44    fn default() -> Self {
45        Self {
46            verification_success_ns: MeasurementSummary::EMPTY,
47            verification_failure_ns: MeasurementSummary::EMPTY,
48            upload_metadata_success_ns: MeasurementSummary::EMPTY,
49            upload_metadata_failure_ns: MeasurementSummary::EMPTY,
50            upload_data_success_ns: MeasurementSummary::EMPTY,
51            upload_data_failure_ns: MeasurementSummary::EMPTY,
52            prepared_chunk_bytes: EMPTY_CHUNK_HISTOGRAM,
53        }
54    }
55}
56
57impl IcSnapshotLocalMetrics {
58    /// Read successful local IC-tree verification durations in nanoseconds.
59    #[must_use]
60    pub const fn verification_success_ns(self) -> MeasurementSummary {
61        self.verification_success_ns
62    }
63    /// Read rejected local IC-tree verification durations in nanoseconds.
64    #[must_use]
65    pub const fn verification_failure_ns(self) -> MeasurementSummary {
66        self.verification_failure_ns
67    }
68    /// Read successful local upload-metadata preparation durations in nanoseconds.
69    #[must_use]
70    pub const fn upload_metadata_success_ns(self) -> MeasurementSummary {
71        self.upload_metadata_success_ns
72    }
73    /// Read rejected local upload-metadata preparation durations in nanoseconds.
74    #[must_use]
75    pub const fn upload_metadata_failure_ns(self) -> MeasurementSummary {
76        self.upload_metadata_failure_ns
77    }
78    /// Read successful local upload-data preparation durations in nanoseconds.
79    #[must_use]
80    pub const fn upload_data_success_ns(self) -> MeasurementSummary {
81        self.upload_data_success_ns
82    }
83    /// Read rejected local upload-data preparation durations in nanoseconds.
84    #[must_use]
85    pub const fn upload_data_failure_ns(self) -> MeasurementSummary {
86        self.upload_data_failure_ns
87    }
88    /// Read successful prepared chunk sizes in bytes, including zero and repeated work.
89    #[must_use]
90    pub const fn prepared_chunk_bytes(self) -> MeasurementSummary {
91        self.prepared_chunk_bytes.summary()
92    }
93    /// Read the distribution of successful prepared chunk sizes in bytes.
94    ///
95    /// Inclusive upper bounds are zero, 32 KiB, 256 KiB and 1 MiB. Disjoint
96    /// buckets distinguish empty chunks, small extents, larger extents and the
97    /// supported maximum. Overflow is separate; admitted payloads cannot exceed
98    /// 1 MiB. Repeated preparation is another sample, not unique progress.
99    /// Counts saturate independently; ranges are not exact percentiles.
100    /// The histogram owns the summary returned by [`Self::prepared_chunk_bytes`].
101    #[must_use]
102    pub const fn prepared_chunk_bytes_histogram(self) -> MeasurementHistogram<4> {
103        self.prepared_chunk_bytes
104    }
105
106    fn record(
107        &mut self,
108        operation: LocalOperation,
109        elapsed: Duration,
110        succeeded: bool,
111        chunk_bytes: Option<usize>,
112    ) {
113        let duration = u64::try_from(elapsed.as_nanos()).unwrap_or(u64::MAX);
114        let summary = match (operation, succeeded) {
115            (LocalOperation::Verification, true) => &mut self.verification_success_ns,
116            (LocalOperation::Verification, false) => &mut self.verification_failure_ns,
117            (LocalOperation::UploadMetadata, true) => &mut self.upload_metadata_success_ns,
118            (LocalOperation::UploadMetadata, false) => &mut self.upload_metadata_failure_ns,
119            (LocalOperation::UploadData, true) => &mut self.upload_data_success_ns,
120            (LocalOperation::UploadData, false) => &mut self.upload_data_failure_ns,
121        };
122        summary.record(duration);
123        if let (LocalOperation::UploadData, true, Some(bytes)) = (operation, succeeded, chunk_bytes)
124        {
125            self.prepared_chunk_bytes
126                .record(u64::try_from(bytes).unwrap_or(u64::MAX));
127        }
128    }
129}
130
131#[derive(Clone, Copy)]
132pub(super) enum LocalOperation {
133    Verification,
134    UploadMetadata,
135    UploadData,
136}
137
138impl DownloadJournalGuard<'_> {
139    /// Read a copied local diagnostic snapshot without filesystem IO or fresh checks.
140    ///
141    /// Sampling is per guard lifetime, including rejected calls; ordinary journal
142    /// replay never supplies samples. Poison recovery is diagnostic only and cannot
143    /// change an operation result. No labels, IDs, byte contents or global registry
144    /// are retained. Host monotonic durations measure inclusive local work, not IC
145    /// instructions/cycles, unique transfer progress or an authoritative receipt.
146    #[must_use]
147    pub fn ic_snapshot_metrics(&self) -> IcSnapshotLocalMetrics {
148        *self
149            .ic_snapshot_metrics
150            .lock()
151            .unwrap_or_else(PoisonError::into_inner)
152    }
153
154    pub(super) fn record_ic_snapshot_metrics(
155        &self,
156        operation: LocalOperation,
157        started: Instant,
158        succeeded: bool,
159        chunk_bytes: Option<usize>,
160    ) {
161        let elapsed = started.elapsed();
162        self.ic_snapshot_metrics
163            .lock()
164            .unwrap_or_else(PoisonError::into_inner)
165            .record(operation, elapsed, succeeded, chunk_bytes);
166    }
167}
168
169#[cfg(test)]
170mod tests;