Skip to main content

glean_core/metrics/
counter.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 std::cmp::Ordering;
6use std::sync::Arc;
7
8use crate::common_metric_data::{CommonMetricDataInternal, MetricLabel};
9use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
10use crate::metrics::Metric;
11use crate::metrics::MetricType;
12use crate::Glean;
13use crate::{CommonMetricData, TestGetValue};
14
15/// A counter metric.
16///
17/// Used to count things.
18/// The value can only be incremented, not decremented.
19#[derive(Clone, Debug)]
20pub struct CounterMetric {
21    meta: Arc<CommonMetricDataInternal>,
22}
23
24impl MetricType for CounterMetric {
25    fn meta(&self) -> &CommonMetricDataInternal {
26        &self.meta
27    }
28
29    fn with_name(&self, name: String) -> Self {
30        let mut meta = (*self.meta).clone();
31        meta.inner.name = name;
32        Self {
33            meta: Arc::new(meta),
34        }
35    }
36
37    fn with_label(&self, label: MetricLabel) -> Self {
38        let mut meta = (*self.meta).clone();
39        meta.inner.label = Some(label);
40        Self {
41            meta: Arc::new(meta),
42        }
43    }
44}
45
46// IMPORTANT:
47//
48// When changing this implementation, make sure all the operations are
49// also declared in the related trait in `../traits/`.
50impl CounterMetric {
51    /// Creates a new counter metric.
52    pub fn new(meta: CommonMetricData) -> Self {
53        Self {
54            meta: Arc::new(meta.into()),
55        }
56    }
57
58    /// Increases the counter by `amount` synchronously.
59    #[doc(hidden)]
60    pub fn add_sync(&self, glean: &Glean, amount: i32) {
61        if !self.should_record(glean) {
62            return;
63        }
64
65        match amount.cmp(&0) {
66            Ordering::Less => {
67                record_error(
68                    glean,
69                    &self.meta,
70                    ErrorType::InvalidValue,
71                    format!("Added negative value {}", amount),
72                    None,
73                );
74                return;
75            }
76            Ordering::Equal => {
77                // Silently ignore.
78                return;
79            }
80            Ordering::Greater => (),
81        };
82
83        // Let's be defensive here:
84        // The uploader tries to store a counter metric,
85        // but in tests that storage might be gone already.
86        // Let's just ignore those.
87        // This should never happen in real app usage.
88        if let Some(storage) = glean.storage_opt() {
89            storage.record_with(glean, &self.meta, |old_value| match old_value {
90                Some(Metric::Counter(old_value)) => {
91                    Metric::Counter(old_value.saturating_add(amount))
92                }
93                _ => Metric::Counter(amount),
94            })
95        } else {
96            log::warn!(
97                "Couldn't get storage. Can't record counter '{}'.",
98                self.meta.base_identifier()
99            );
100        }
101    }
102
103    /// Increases the counter by `amount`.
104    ///
105    /// # Arguments
106    ///
107    /// * `amount` - The amount to increase by. Should be positive.
108    ///
109    /// ## Notes
110    ///
111    /// Logs an error if the `amount` is 0 or negative.
112    pub fn add(&self, amount: i32) {
113        let metric = self.clone();
114        crate::launch_with_glean(move |glean| metric.add_sync(glean, amount))
115    }
116
117    /// Get current value
118    #[doc(hidden)]
119    pub fn get_value<'a, S: Into<Option<&'a str>>>(
120        &self,
121        glean: &Glean,
122        ping_name: S,
123    ) -> Option<i32> {
124        let queried_ping_name = ping_name
125            .into()
126            .unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
127
128        match glean.storage().get_metric(self.meta(), queried_ping_name) {
129            Some(Metric::Counter(i)) => Some(i),
130            _ => None,
131        }
132    }
133
134    /// **Exported for test purposes.**
135    ///
136    /// Gets the number of recorded errors for the given metric and error type.
137    ///
138    /// # Arguments
139    ///
140    /// * `error` - The type of error
141    ///
142    /// # Returns
143    ///
144    /// The number of errors reported.
145    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
146        crate::block_on_dispatcher();
147
148        crate::core::with_glean(|glean| {
149            test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
150        })
151    }
152}
153
154impl TestGetValue for CounterMetric {
155    type Output = i32;
156
157    /// **Test-only API (exported for FFI purposes).**
158    ///
159    /// Gets the currently stored value as an integer.
160    ///
161    /// This doesn't clear the stored value.
162    ///
163    /// # Arguments
164    ///
165    /// * `ping_name` - the optional name of the ping to retrieve the metric
166    ///                 for. Defaults to the first value in `send_in_pings`.
167    ///
168    /// # Returns
169    ///
170    /// The stored value or `None` if nothing stored.
171    fn test_get_value(&self, ping_name: Option<String>) -> Option<i32> {
172        crate::block_on_dispatcher();
173        crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
174    }
175}