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