Skip to main content

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