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