Skip to main content

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