1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
45use std::time::Instant;
67/// Single sample for a Glean labeled_timing_distribution
8#[derive(uniffi::Record)]
9pub struct LabeledTimingSample {
10pub label: String,
11/// Time in microseconds
12pub value: u64,
13}
1415impl LabeledTimingSample {
16fn new(label: String, value: u64) -> Self {
17Self { label, value }
18 }
19}
2021/// Ingestion metrics
22///
23/// These are recorded during [crate::Store::ingest] and returned to the consumer to record.
24#[derive(Default, uniffi::Record)]
25pub struct SuggestIngestionMetrics {
26/// Samples for the `suggest.ingestion_time` metric
27pub ingestion_times: Vec<LabeledTimingSample>,
28/// Samples for the `suggest.ingestion_download_time` metric
29pub download_times: Vec<LabeledTimingSample>,
30}
3132impl SuggestIngestionMetrics {
33/// Wraps each iteration in `ingest` and records the time for it.
34 ///
35 /// Passes the closure a `&mut MetricsContext`.
36pub fn measure_ingest<F, T>(&mut self, record_type: impl Into<String>, operation: F) -> T
37where
38F: FnOnce(&mut MetricsContext) -> T,
39 {
40let timer = Instant::now();
41let record_type = record_type.into();
42let mut context = MetricsContext::default();
43let result = operation(&mut context);
44let elapsed = timer.elapsed().as_micros() as u64;
45match context {
46 MetricsContext::Uninstrumented => (),
47 MetricsContext::Instrumented { download_time } => {
48self.ingestion_times.push(LabeledTimingSample::new(
49 record_type.clone(),
50 elapsed - download_time,
51 ));
52self.download_times
53 .push(LabeledTimingSample::new(record_type, download_time));
54 }
55 }
56 result
57 }
58}
5960/// Context for a ingestion measurement
61#[derive(Default)]
62pub enum MetricsContext {
63/// Default state, if this is the state at the end of `measure_ingest`, then it means no work
64 /// was done and we should not record anything
65#[default]
66Uninstrumented,
67/// State after `measure_download()` is called. We currently always download an attachment
68 /// whenever we do any ingestion work, so we can use this a test for if we should record
69 /// anything.
70Instrumented { download_time: u64 },
71}
7273impl MetricsContext {
74/// Tracks download times during the ingestion
75 ///
76 /// We report download times as a separate metric [Self::measure_download] can be called
77 /// multiple times. [DownloadTimer] will track the total time for all calls.
78pub fn measure_download<F, T>(&mut self, operation: F) -> T
79where
80F: FnOnce() -> T,
81 {
82let timer = Instant::now();
83let result = operation();
84let elasped = timer.elapsed().as_micros() as u64;
85match self {
86Self::Uninstrumented => {
87*self = Self::Instrumented {
88 download_time: elasped,
89 }
90 }
91Self::Instrumented { download_time } => *download_time += elasped,
92 }
93 result
94 }
95}
9697/// Query metrics
98///
99/// These are recorded during [crate::Store::query] and returned to the consumer to record.
100#[derive(Default)]
101pub struct SuggestQueryMetrics {
102pub times: Vec<LabeledTimingSample>,
103}
104105impl SuggestQueryMetrics {
106pub fn measure_query<F, T>(&mut self, provider: impl Into<String>, operation: F) -> T
107where
108F: FnOnce() -> T,
109 {
110let provider = provider.into();
111let timer = Instant::now();
112// Make sure the compiler doesn't reorder/inline in a way that invalidates this
113 // measurement.
114let result = std::hint::black_box(operation());
115let elapsed = timer.elapsed().as_micros() as u64;
116self.times.push(LabeledTimingSample::new(provider, elapsed));
117 result
118 }
119}