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