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(
99            #[cfg(not(feature = "sqlite"))]
100            glean,
101            self.meta(),
102            queried_ping_name,
103        ) {
104            Some(Metric::String(s)) => Some(s),
105            _ => None,
106        }
107    }
108
109    /// **Exported for test purposes.**
110    ///
111    /// Gets the number of recorded errors for the given metric and error type.
112    ///
113    /// # Arguments
114    ///
115    /// * `error` - The type of error
116    ///
117    /// # Returns
118    ///
119    /// The number of errors reported.
120    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
121        crate::block_on_dispatcher();
122
123        crate::core::with_glean(|glean| {
124            test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
125        })
126    }
127}
128
129impl TestGetValue for StringMetric {
130    type Output = String;
131
132    /// **Test-only API (exported for FFI purposes).**
133    ///
134    /// Gets the currently stored value as a string.
135    ///
136    /// This doesn't clear the stored value.
137    ///
138    /// # Arguments
139    ///
140    /// * `ping_name` - the optional name of the ping to retrieve the metric
141    ///                 for. Defaults to the first value in `send_in_pings`.
142    ///
143    /// # Returns
144    ///
145    /// The stored value or `None` if nothing stored.
146    fn test_get_value(&self, ping_name: Option<String>) -> Option<String> {
147        crate::block_on_dispatcher();
148        crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
149    }
150}
151
152#[cfg(test)]
153mod test {
154    use super::*;
155    use crate::tests::new_glean;
156    use crate::util::truncate_string_at_boundary;
157    use crate::Lifetime;
158
159    #[test]
160    fn setting_a_long_string_records_an_error() {
161        let (glean, _t) = new_glean(None);
162
163        let metric = StringMetric::new(CommonMetricData {
164            name: "string_metric".into(),
165            category: "test".into(),
166            send_in_pings: vec!["store1".into()],
167            lifetime: Lifetime::Application,
168            disabled: false,
169            label: None,
170            in_session: false,
171        });
172
173        let sample_string = "0123456789".repeat(26);
174        metric.set_sync(&glean, sample_string.clone());
175
176        let truncated = truncate_string_at_boundary(sample_string, MAX_LENGTH_VALUE);
177        assert_eq!(truncated, metric.get_value(&glean, "store1").unwrap());
178
179        assert_eq!(
180            1,
181            test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidOverflow)
182                .unwrap()
183        );
184    }
185}