glean_core/metrics/
string.rs1use 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#[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
49impl StringMetric {
54    pub fn new(meta: CommonMetricData) -> Self {
56        Self {
57            meta: Arc::new(meta.into()),
58        }
59    }
60
61    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    #[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    #[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    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    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}