glean_core/metrics/
rate.rs

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 https://mozilla.org/MPL/2.0/.
4
5use crate::common_metric_data::CommonMetricDataInternal;
6use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
7use crate::metrics::Metric;
8use crate::metrics::MetricType;
9use crate::storage::StorageManager;
10use crate::Glean;
11use crate::{CommonMetricData, TestGetValue};
12
13/// A rate value as given by its numerator and denominator.
14#[derive(Debug, Clone, Eq, PartialEq)]
15pub struct Rate {
16    /// A rate's numerator
17    pub numerator: i32,
18    /// A rate's denominator
19    pub denominator: i32,
20}
21
22impl From<(i32, i32)> for Rate {
23    fn from((num, den): (i32, i32)) -> Self {
24        Self {
25            numerator: num,
26            denominator: den,
27        }
28    }
29}
30
31/// A rate metric.
32///
33/// Used to determine the proportion of things via two counts:
34/// * A numerator defining the amount of times something happened,
35/// * A denominator counting the amount of times someting could have happened.
36///
37/// Both numerator and denominator can only be incremented, not decremented.
38#[derive(Clone, Debug)]
39pub struct RateMetric {
40    meta: CommonMetricDataInternal,
41}
42
43impl MetricType for RateMetric {
44    fn meta(&self) -> &CommonMetricDataInternal {
45        &self.meta
46    }
47}
48
49// IMPORTANT:
50//
51// When changing this implementation, make sure all the operations are
52// also declared in the related trait in `../traits/`.
53impl RateMetric {
54    /// Creates a new rate metric.
55    pub fn new(meta: CommonMetricData) -> Self {
56        Self { meta: meta.into() }
57    }
58
59    /// Increases the numerator by `amount`.
60    ///
61    /// # Arguments
62    ///
63    /// * `glean` - The Glean instance this metric belongs to.
64    /// * `amount` - The amount to increase by. Should be non-negative.
65    ///
66    /// ## Notes
67    ///
68    /// Logs an error if the `amount` is negative.
69    pub fn add_to_numerator(&self, amount: i32) {
70        let metric = self.clone();
71        crate::launch_with_glean(move |glean| metric.add_to_numerator_sync(glean, amount))
72    }
73
74    #[doc(hidden)]
75    pub fn add_to_numerator_sync(&self, glean: &Glean, amount: i32) {
76        if !self.should_record(glean) {
77            return;
78        }
79
80        if amount < 0 {
81            record_error(
82                glean,
83                &self.meta,
84                ErrorType::InvalidValue,
85                format!("Added negative value {} to numerator", amount),
86                None,
87            );
88            return;
89        }
90
91        glean
92            .storage()
93            .record_with(glean, &self.meta, |old_value| match old_value {
94                Some(Metric::Rate(num, den)) => Metric::Rate(num.saturating_add(amount), den),
95                _ => Metric::Rate(amount, 0), // Denominator will show up eventually. Probably.
96            });
97    }
98
99    /// Increases the denominator by `amount`.
100    ///
101    /// # Arguments
102    ///
103    /// * `glean` - The Glean instance this metric belongs to.
104    /// * `amount` - The amount to increase by. Should be non-negative.
105    ///
106    /// ## Notes
107    ///
108    /// Logs an error if the `amount` is negative.
109    pub fn add_to_denominator(&self, amount: i32) {
110        let metric = self.clone();
111        crate::launch_with_glean(move |glean| metric.add_to_denominator_sync(glean, amount))
112    }
113
114    #[doc(hidden)]
115    pub fn add_to_denominator_sync(&self, glean: &Glean, amount: i32) {
116        if !self.should_record(glean) {
117            return;
118        }
119
120        if amount < 0 {
121            record_error(
122                glean,
123                &self.meta,
124                ErrorType::InvalidValue,
125                format!("Added negative value {} to denominator", amount),
126                None,
127            );
128            return;
129        }
130
131        glean
132            .storage()
133            .record_with(glean, &self.meta, |old_value| match old_value {
134                Some(Metric::Rate(num, den)) => Metric::Rate(num, den.saturating_add(amount)),
135                _ => Metric::Rate(0, amount),
136            });
137    }
138
139    /// Get current value
140    #[doc(hidden)]
141    pub fn get_value<'a, S: Into<Option<&'a str>>>(
142        &self,
143        glean: &Glean,
144        ping_name: S,
145    ) -> Option<Rate> {
146        let queried_ping_name = ping_name
147            .into()
148            .unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
149
150        match StorageManager.snapshot_metric_for_test(
151            glean.storage(),
152            queried_ping_name,
153            &self.meta.identifier(glean),
154            self.meta.inner.lifetime,
155        ) {
156            Some(Metric::Rate(n, d)) => Some((n, d).into()),
157            _ => None,
158        }
159    }
160
161    /// **Exported for test purposes.**
162    ///
163    /// Gets the number of recorded errors for the given metric and error type.
164    ///
165    /// # Arguments
166    ///
167    /// * `error` - The type of error
168    ///
169    /// # Returns
170    ///
171    /// The number of errors reported.
172    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
173        crate::block_on_dispatcher();
174
175        crate::core::with_glean(|glean| {
176            test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
177        })
178    }
179}
180
181impl TestGetValue<Rate> for RateMetric {
182    /// **Test-only API (exported for FFI purposes).**
183    ///
184    /// Gets the currently stored value as a pair of integers.
185    ///
186    /// This doesn't clear the stored value.
187    ///
188    /// # Arguments
189    ///
190    /// * `ping_name` - the optional name of the ping to retrieve the metric
191    ///                 for. Defaults to the first value in `send_in_pings`.
192    ///
193    /// # Returns
194    ///
195    /// The stored value or `None` if nothing stored.
196    fn test_get_value(&self, ping_name: Option<String>) -> Option<Rate> {
197        crate::block_on_dispatcher();
198        crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
199    }
200}