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        self.set_sync_with_max_length(glean, value, MAX_LENGTH_VALUE)
78    }
79
80    /// Sets the value synchronously with a custom byte limit for internal metrics.
81    pub(crate) fn set_sync_with_max_length<S: Into<String>>(
82        &self,
83        glean: &Glean,
84        value: S,
85        max_length: usize,
86    ) {
87        if !self.should_record(glean) {
88            return;
89        }
90
91        let s = truncate_string_at_boundary_with_error(glean, &self.meta, value, max_length);
92
93        let value = Metric::String(s);
94        glean.storage().record(glean, &self.meta, &value)
95    }
96
97    /// Gets the current-stored value as a string, or None if there is no value.
98    #[doc(hidden)]
99    pub fn get_value<'a, S: Into<Option<&'a str>>>(
100        &self,
101        glean: &Glean,
102        ping_name: S,
103    ) -> Option<String> {
104        let queried_ping_name = ping_name
105            .into()
106            .unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
107
108        match glean.storage().get_metric(
109            #[cfg(not(feature = "sqlite"))]
110            glean,
111            self.meta(),
112            queried_ping_name,
113        ) {
114            Some(Metric::String(s)) => Some(s),
115            _ => None,
116        }
117    }
118
119    /// **Exported for test purposes.**
120    ///
121    /// Gets the number of recorded errors for the given metric and error type.
122    ///
123    /// # Arguments
124    ///
125    /// * `error` - The type of error
126    ///
127    /// # Returns
128    ///
129    /// The number of errors reported.
130    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
131        crate::block_on_dispatcher();
132
133        crate::core::with_glean(|glean| self.test_get_num_recorded_errors_sync(glean, error))
134    }
135
136    /// Gets the number of recorded errors synchronously for the given Glean instance.
137    pub(crate) fn test_get_num_recorded_errors_sync(&self, glean: &Glean, error: ErrorType) -> i32 {
138        test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
139    }
140}
141
142impl TestGetValue for StringMetric {
143    type Output = String;
144
145    /// **Test-only API (exported for FFI purposes).**
146    ///
147    /// Gets the currently stored value as a string.
148    ///
149    /// This doesn't clear the stored value.
150    ///
151    /// # Arguments
152    ///
153    /// * `ping_name` - the optional name of the ping to retrieve the metric
154    ///                 for. Defaults to the first value in `send_in_pings`.
155    ///
156    /// # Returns
157    ///
158    /// The stored value or `None` if nothing stored.
159    fn test_get_value(&self, ping_name: Option<String>) -> Option<String> {
160        crate::block_on_dispatcher();
161        crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
162    }
163}
164
165#[cfg(test)]
166mod test {
167    use super::*;
168    use crate::tests::new_glean;
169    use crate::util::truncate_string_at_boundary;
170    use crate::Lifetime;
171
172    #[test]
173    fn setting_a_long_string_records_an_error() {
174        let (glean, _t) = new_glean(None);
175
176        let metric = StringMetric::new(CommonMetricData {
177            name: "string_metric".into(),
178            category: "test".into(),
179            send_in_pings: vec!["store1".into()],
180            lifetime: Lifetime::Application,
181            disabled: false,
182            label: None,
183            in_session: false,
184        });
185
186        let sample_string = "0123456789".repeat(26);
187        metric.set_sync(&glean, sample_string.clone());
188
189        let truncated = truncate_string_at_boundary(sample_string, MAX_LENGTH_VALUE);
190        assert_eq!(truncated, metric.get_value(&glean, "store1").unwrap());
191
192        assert_eq!(
193            1,
194            test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidOverflow)
195                .unwrap()
196        );
197    }
198}