Skip to main content

glean_core/metrics/
string.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::sync::Arc;
6
7use crate::common_metric_data::{CommonMetricDataInternal, MetricLabel};
8use crate::error_recording::{test_get_num_recorded_errors, ErrorType};
9use crate::metrics::Metric;
10use crate::metrics::MetricType;
11use crate::util::truncate_string_at_boundary_with_error;
12use crate::Glean;
13use crate::{CommonMetricData, TestGetValue};
14
15pub const MAX_LENGTH_VALUE: usize = 255;
16
17/// A string metric.
18///
19/// Record an Unicode string value with arbitrary content.
20/// Strings are length-limited to `MAX_LENGTH_VALUE` bytes.
21#[derive(Clone, Debug)]
22pub struct StringMetric {
23    meta: Arc<CommonMetricDataInternal>,
24}
25
26impl MetricType for StringMetric {
27    fn meta(&self) -> &CommonMetricDataInternal {
28        &self.meta
29    }
30
31    fn with_name(&self, name: String) -> Self {
32        let mut meta = (*self.meta).clone();
33        meta.inner.name = name;
34        Self {
35            meta: Arc::new(meta),
36        }
37    }
38
39    fn with_label(&self, label: MetricLabel) -> Self {
40        let mut meta = (*self.meta).clone();
41        meta.inner.label = Some(label);
42        Self {
43            meta: Arc::new(meta),
44        }
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 StringMetric {
53    /// Creates a new string metric.
54    pub fn new(meta: CommonMetricData) -> Self {
55        Self {
56            meta: Arc::new(meta.into()),
57        }
58    }
59
60    /// Sets to the specified value.
61    ///
62    /// # Arguments
63    ///
64    /// * `value` - The string to set the metric to.
65    ///
66    /// ## Notes
67    ///
68    /// Truncates the value if it is longer than `MAX_LENGTH_VALUE` bytes and logs an error.
69    pub fn set(&self, value: String) {
70        let metric = self.clone();
71        crate::launch_with_glean(move |glean| metric.set_sync(glean, &value))
72    }
73
74    /// Sets to the specified value synchronously.
75    #[doc(hidden)]
76    pub fn set_sync<S: Into<String>>(&self, glean: &Glean, value: S) {
77        if !self.should_record(glean) {
78            return;
79        }
80
81        let s = truncate_string_at_boundary_with_error(glean, &self.meta, value, MAX_LENGTH_VALUE);
82
83        let value = Metric::String(s);
84        glean.storage().record(glean, &self.meta, &value)
85    }
86
87    /// Gets the current-stored value as a string, or None if there is no value.
88    #[doc(hidden)]
89    pub fn get_value<'a, S: Into<Option<&'a str>>>(
90        &self,
91        glean: &Glean,
92        ping_name: S,
93    ) -> Option<String> {
94        let queried_ping_name = ping_name
95            .into()
96            .unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
97
98        match glean.storage().get_metric(self.meta(), queried_ping_name) {
99            Some(Metric::String(s)) => Some(s),
100            _ => None,
101        }
102    }
103
104    /// **Exported for test purposes.**
105    ///
106    /// Gets the number of recorded errors for the given metric and error type.
107    ///
108    /// # Arguments
109    ///
110    /// * `error` - The type of error
111    ///
112    /// # Returns
113    ///
114    /// The number of errors reported.
115    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
116        crate::block_on_dispatcher();
117
118        crate::core::with_glean(|glean| {
119            test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
120        })
121    }
122}
123
124impl TestGetValue for StringMetric {
125    type Output = String;
126
127    /// **Test-only API (exported for FFI purposes).**
128    ///
129    /// Gets the currently stored value as a string.
130    ///
131    /// This doesn't clear the stored value.
132    ///
133    /// # Arguments
134    ///
135    /// * `ping_name` - the optional name of the ping to retrieve the metric
136    ///                 for. Defaults to the first value in `send_in_pings`.
137    ///
138    /// # Returns
139    ///
140    /// The stored value or `None` if nothing stored.
141    fn test_get_value(&self, ping_name: Option<String>) -> Option<String> {
142        crate::block_on_dispatcher();
143        crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
144    }
145}
146
147#[cfg(test)]
148mod test {
149    use super::*;
150    use crate::tests::new_glean;
151    use crate::util::truncate_string_at_boundary;
152    use crate::Lifetime;
153
154    #[test]
155    fn setting_a_long_string_records_an_error() {
156        let (glean, _t) = new_glean(None);
157
158        let metric = StringMetric::new(CommonMetricData {
159            name: "string_metric".into(),
160            category: "test".into(),
161            send_in_pings: vec!["store1".into()],
162            lifetime: Lifetime::Application,
163            disabled: false,
164            label: None,
165            in_session: false,
166        });
167
168        let sample_string = "0123456789".repeat(26);
169        metric.set_sync(&glean, sample_string.clone());
170
171        let truncated = truncate_string_at_boundary(sample_string, MAX_LENGTH_VALUE);
172        assert_eq!(truncated, metric.get_value(&glean, "store1").unwrap());
173
174        assert_eq!(
175            1,
176            test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidOverflow)
177                .unwrap()
178        );
179    }
180}