glean_core/metrics/
string_list.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::{record_error, 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
16// Maximum length of any list
17const MAX_LIST_LENGTH: usize = 100;
18// Maximum length of any string in the list
19const MAX_STRING_LENGTH: usize = 100;
20
21/// A string list metric.
22///
23/// This allows appending a string value with arbitrary content to a list.
24#[derive(Clone, Debug)]
25pub struct StringListMetric {
26    meta: Arc<CommonMetricDataInternal>,
27}
28
29impl MetricType for StringListMetric {
30    fn meta(&self) -> &CommonMetricDataInternal {
31        &self.meta
32    }
33}
34
35// IMPORTANT:
36//
37// When changing this implementation, make sure all the operations are
38// also declared in the related trait in `../traits/`.
39impl StringListMetric {
40    /// Creates a new string list metric.
41    pub fn new(meta: CommonMetricData) -> Self {
42        Self {
43            meta: Arc::new(meta.into()),
44        }
45    }
46
47    /// Adds a new string to the list.
48    ///
49    /// # Arguments
50    ///
51    /// * `value` - The string to add.
52    ///
53    /// ## Notes
54    ///
55    /// Truncates the value if it is longer than `MAX_STRING_LENGTH` bytes and logs an error.
56    pub fn add(&self, value: String) {
57        let metric = self.clone();
58        crate::launch_with_glean(move |glean| metric.add_sync(glean, value))
59    }
60
61    /// Adds a new string to the list synchronously
62    #[doc(hidden)]
63    pub fn add_sync<S: Into<String>>(&self, glean: &Glean, value: S) {
64        if !self.should_record(glean) {
65            return;
66        }
67
68        let value =
69            truncate_string_at_boundary_with_error(glean, &self.meta, value, MAX_STRING_LENGTH);
70        let mut error = None;
71        glean
72            .storage()
73            .record_with(glean, &self.meta, |old_value| match old_value {
74                Some(Metric::StringList(mut old_value)) => {
75                    if old_value.len() == MAX_LIST_LENGTH {
76                        let msg = format!(
77                            "String list length of {} exceeds maximum of {}",
78                            old_value.len() + 1,
79                            MAX_LIST_LENGTH
80                        );
81                        error = Some(msg);
82                    } else {
83                        old_value.push(value.clone());
84                    }
85                    Metric::StringList(old_value)
86                }
87                _ => Metric::StringList(vec![value.clone()]),
88            });
89
90        if let Some(msg) = error {
91            record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
92        }
93    }
94
95    /// Sets to a specific list of strings.
96    ///
97    /// # Arguments
98    ///
99    /// * `value` - The list of string to set the metric to.
100    ///
101    /// ## Notes
102    ///
103    /// If passed an empty list, records an error and returns.
104    ///
105    /// Truncates the list if it is longer than `MAX_LIST_LENGTH` and logs an error.
106    ///
107    /// Truncates any value in the list if it is longer than `MAX_STRING_LENGTH` and logs an error.
108    pub fn set(&self, values: Vec<String>) {
109        let metric = self.clone();
110        crate::launch_with_glean(move |glean| metric.set_sync(glean, values))
111    }
112
113    /// Sets to a specific list of strings synchronously.
114    #[doc(hidden)]
115    pub fn set_sync(&self, glean: &Glean, value: Vec<String>) {
116        if !self.should_record(glean) {
117            return;
118        }
119
120        let value = if value.len() > MAX_LIST_LENGTH {
121            let msg = format!(
122                "StringList length {} exceeds maximum of {}",
123                value.len(),
124                MAX_LIST_LENGTH
125            );
126            record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
127            value[0..MAX_LIST_LENGTH].to_vec()
128        } else {
129            value
130        };
131
132        let value = value
133            .into_iter()
134            .map(|elem| {
135                truncate_string_at_boundary_with_error(glean, &self.meta, elem, MAX_STRING_LENGTH)
136            })
137            .collect();
138
139        let value = Metric::StringList(value);
140        glean.storage().record(glean, &self.meta, &value);
141    }
142
143    /// **Test-only API (exported for FFI purposes).**
144    ///
145    /// Gets the currently-stored values.
146    ///
147    /// This doesn't clear the stored value.
148    #[doc(hidden)]
149    pub fn get_value<'a, S: Into<Option<&'a str>>>(
150        &self,
151        glean: &Glean,
152        ping_name: S,
153    ) -> Option<Vec<String>> {
154        let queried_ping_name = ping_name
155            .into()
156            .unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
157
158        match StorageManager.snapshot_metric_for_test(
159            glean.storage(),
160            queried_ping_name,
161            &self.meta.identifier(glean),
162            self.meta.inner.lifetime,
163        ) {
164            Some(Metric::StringList(values)) => Some(values),
165            _ => None,
166        }
167    }
168
169    /// **Exported for test purposes.**
170    ///
171    /// Gets the number of recorded errors for the given metric and error type.
172    ///
173    /// # Arguments
174    ///
175    /// * `error` - The type of error
176    ///
177    /// # Returns
178    ///
179    /// The number of errors reported.
180    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
181        crate::block_on_dispatcher();
182
183        crate::core::with_glean(|glean| {
184            test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
185        })
186    }
187}
188
189impl TestGetValue<Vec<String>> for StringListMetric {
190    /// **Test-only API (exported for FFI purposes).**
191    ///
192    /// Gets the currently-stored values.
193    ///
194    /// This doesn't clear the stored value.
195    ///
196    /// # Arguments
197    ///
198    /// * `ping_name` - the optional name of the ping to retrieve the metric
199    ///                 for. Defaults to the first value in `send_in_pings`.
200    ///
201    /// # Returns
202    ///
203    /// The stored value or `None` if nothing stored.
204    fn test_get_value(&self, ping_name: Option<String>) -> Option<Vec<String>> {
205        crate::block_on_dispatcher();
206        crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()))
207    }
208}