1use std::borrow::Cow;
6use std::collections::{hash_map::Entry, HashMap};
7use std::sync::{Arc, Mutex};
8
9use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal};
10use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
11use crate::histogram::HistogramType;
12use crate::metrics::{
13 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
14 Metric, MetricType, QuantityMetric, StringMetric, TimeUnit, TimingDistributionMetric,
15};
16use crate::Glean;
17
18const MAX_LABELS: usize = 16;
19const OTHER_LABEL: &str = "__other__";
20const MAX_LABEL_LENGTH: usize = 71;
21
22pub type LabeledCounter = LabeledMetric<CounterMetric>;
24
25pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
27
28pub type LabeledString = LabeledMetric<StringMetric>;
30
31pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
33
34pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
36
37pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
39
40pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
42
43pub enum LabeledMetricData {
48 #[allow(missing_docs)]
50 Common { cmd: CommonMetricData },
51 #[allow(missing_docs)]
53 CustomDistribution {
54 cmd: CommonMetricData,
55 range_min: i64,
56 range_max: i64,
57 bucket_count: i64,
58 histogram_type: HistogramType,
59 },
60 #[allow(missing_docs)]
62 MemoryDistribution {
63 cmd: CommonMetricData,
64 unit: MemoryUnit,
65 },
66 #[allow(missing_docs)]
68 TimingDistribution {
69 cmd: CommonMetricData,
70 unit: TimeUnit,
71 },
72}
73
74#[derive(Debug)]
78pub struct LabeledMetric<T> {
79 labels: Option<Vec<Cow<'static, str>>>,
80 submetric: T,
83
84 label_map: Mutex<HashMap<String, Arc<T>>>,
87}
88
89mod private {
93 use super::LabeledMetricData;
94 use crate::metrics::{
95 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
96 QuantityMetric, StringMetric, TimingDistributionMetric,
97 };
98
99 pub trait Sealed {
105 fn new_inner(meta: LabeledMetricData) -> Self;
107 }
108
109 impl Sealed for CounterMetric {
110 fn new_inner(meta: LabeledMetricData) -> Self {
111 match meta {
112 LabeledMetricData::Common { cmd } => Self::new(cmd),
113 _ => panic!("Incorrect construction of Labeled<CounterMetric>"),
114 }
115 }
116 }
117
118 impl Sealed for BooleanMetric {
119 fn new_inner(meta: LabeledMetricData) -> Self {
120 match meta {
121 LabeledMetricData::Common { cmd } => Self::new(cmd),
122 _ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
123 }
124 }
125 }
126
127 impl Sealed for StringMetric {
128 fn new_inner(meta: LabeledMetricData) -> Self {
129 match meta {
130 LabeledMetricData::Common { cmd } => Self::new(cmd),
131 _ => panic!("Incorrect construction of Labeled<StringMetric>"),
132 }
133 }
134 }
135
136 impl Sealed for CustomDistributionMetric {
137 fn new_inner(meta: LabeledMetricData) -> Self {
138 match meta {
139 LabeledMetricData::CustomDistribution {
140 cmd,
141 range_min,
142 range_max,
143 bucket_count,
144 histogram_type,
145 } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
146 _ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
147 }
148 }
149 }
150
151 impl Sealed for MemoryDistributionMetric {
152 fn new_inner(meta: LabeledMetricData) -> Self {
153 match meta {
154 LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
155 _ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
156 }
157 }
158 }
159
160 impl Sealed for TimingDistributionMetric {
161 fn new_inner(meta: LabeledMetricData) -> Self {
162 match meta {
163 LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
164 _ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
165 }
166 }
167 }
168
169 impl Sealed for QuantityMetric {
170 fn new_inner(meta: LabeledMetricData) -> Self {
171 match meta {
172 LabeledMetricData::Common { cmd } => Self::new(cmd),
173 _ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
174 }
175 }
176 }
177}
178
179pub trait AllowLabeled: MetricType {
181 fn new_labeled(meta: LabeledMetricData) -> Self;
183}
184
185impl<T> AllowLabeled for T
187where
188 T: MetricType,
189 T: private::Sealed,
190{
191 fn new_labeled(meta: LabeledMetricData) -> Self {
192 T::new_inner(meta)
193 }
194}
195
196impl<T> LabeledMetric<T>
197where
198 T: AllowLabeled + Clone,
199{
200 pub fn new(
204 meta: LabeledMetricData,
205 labels: Option<Vec<Cow<'static, str>>>,
206 ) -> LabeledMetric<T> {
207 let submetric = T::new_labeled(meta);
208 LabeledMetric::new_inner(submetric, labels)
209 }
210
211 fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
212 let label_map = Default::default();
213 LabeledMetric {
214 labels,
215 submetric,
216 label_map,
217 }
218 }
219
220 fn new_metric_with_name(&self, name: String) -> T {
224 self.submetric.with_name(name)
225 }
226
227 fn new_metric_with_dynamic_label(&self, label: String) -> T {
232 self.submetric.with_dynamic_label(label)
233 }
234
235 fn static_label<'a>(&self, label: &'a str) -> &'a str {
250 debug_assert!(self.labels.is_some());
251 let labels = self.labels.as_ref().unwrap();
252 if labels.iter().any(|l| l == label) {
253 label
254 } else {
255 OTHER_LABEL
256 }
257 }
258
259 pub fn get<S: AsRef<str>>(&self, label: S) -> Arc<T> {
271 let label = label.as_ref();
272
273 let id = format!("{}/{}", self.submetric.meta().base_identifier(), label);
276
277 let mut map = self.label_map.lock().unwrap();
278 match map.entry(id) {
279 Entry::Occupied(entry) => Arc::clone(entry.get()),
280 Entry::Vacant(entry) => {
281 let metric = match self.labels {
288 Some(_) => {
289 let label = self.static_label(label);
290 self.new_metric_with_name(combine_base_identifier_and_label(
291 &self.submetric.meta().inner.name,
292 label,
293 ))
294 }
295 None => self.new_metric_with_dynamic_label(label.to_string()),
296 };
297 let metric = Arc::new(metric);
298 entry.insert(Arc::clone(&metric));
299 metric
300 }
301 }
302 }
303
304 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
316 crate::block_on_dispatcher();
317 crate::core::with_glean(|glean| {
318 test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
319 })
320 }
321}
322
323pub fn combine_base_identifier_and_label(base_identifer: &str, label: &str) -> String {
325 format!("{}/{}", base_identifer, label)
326}
327
328pub fn strip_label(identifier: &str) -> &str {
330 identifier.split_once('/').map_or(identifier, |s| s.0)
331}
332
333pub fn validate_dynamic_label(
347 glean: &Glean,
348 meta: &CommonMetricDataInternal,
349 base_identifier: &str,
350 label: &str,
351) -> String {
352 let key = combine_base_identifier_and_label(base_identifier, label);
353 for store in &meta.inner.send_in_pings {
354 if glean.storage().has_metric(meta.inner.lifetime, store, &key) {
355 return key;
356 }
357 }
358
359 let mut label_count = 0;
360 let prefix = &key[..=base_identifier.len()];
361 let mut snapshotter = |_: &[u8], _: &Metric| {
362 label_count += 1;
363 };
364
365 let lifetime = meta.inner.lifetime;
366 for store in &meta.inner.send_in_pings {
367 glean
368 .storage()
369 .iter_store_from(lifetime, store, Some(prefix), &mut snapshotter);
370 }
371
372 let error = if label_count >= MAX_LABELS {
373 true
374 } else if label.len() > MAX_LABEL_LENGTH {
375 let msg = format!(
376 "label length {} exceeds maximum of {}",
377 label.len(),
378 MAX_LABEL_LENGTH
379 );
380 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
381 true
382 } else if label.chars().any(|c| !c.is_ascii() || c.is_ascii_control()) {
383 let msg = format!("label must be printable ascii, got '{}'", label);
384 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
385 true
386 } else {
387 false
388 };
389
390 if error {
391 combine_base_identifier_and_label(base_identifier, OTHER_LABEL)
392 } else {
393 key
394 }
395}