1use std::any::Any;
6use std::borrow::Cow;
7use std::collections::HashSet;
8use std::collections::{hash_map::Entry, HashMap};
9use std::mem;
10use std::sync::{Arc, Mutex};
11
12use malloc_size_of::MallocSizeOf;
13
14use crate::common_metric_data::{CommonMetricData, CommonMetricDataInternal, DynamicLabelType};
15use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
16use crate::histogram::HistogramType;
17use crate::metrics::{
18 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric, MemoryUnit,
19 Metric, MetricType, QuantityMetric, StringMetric, TestGetValue, TimeUnit,
20 TimingDistributionMetric,
21};
22use crate::Glean;
23
24const MAX_LABELS: usize = 16;
25const OTHER_LABEL: &str = "__other__";
26const MAX_LABEL_LENGTH: usize = 111;
27
28pub type LabeledCounter = LabeledMetric<CounterMetric>;
30
31pub type LabeledBoolean = LabeledMetric<BooleanMetric>;
33
34pub type LabeledString = LabeledMetric<StringMetric>;
36
37pub type LabeledCustomDistribution = LabeledMetric<CustomDistributionMetric>;
39
40pub type LabeledMemoryDistribution = LabeledMetric<MemoryDistributionMetric>;
42
43pub type LabeledTimingDistribution = LabeledMetric<TimingDistributionMetric>;
45
46pub type LabeledQuantity = LabeledMetric<QuantityMetric>;
48
49pub enum LabeledMetricData {
54 #[allow(missing_docs)]
56 Common { cmd: CommonMetricData },
57 #[allow(missing_docs)]
59 CustomDistribution {
60 cmd: CommonMetricData,
61 range_min: i64,
62 range_max: i64,
63 bucket_count: i64,
64 histogram_type: HistogramType,
65 },
66 #[allow(missing_docs)]
68 MemoryDistribution {
69 cmd: CommonMetricData,
70 unit: MemoryUnit,
71 },
72 #[allow(missing_docs)]
74 TimingDistribution {
75 cmd: CommonMetricData,
76 unit: TimeUnit,
77 },
78}
79
80#[derive(Debug)]
84pub struct LabeledMetric<T> {
85 labels: Option<Vec<Cow<'static, str>>>,
86 submetric: T,
89
90 label_map: Mutex<HashMap<String, Arc<T>>>,
93}
94
95impl<T: MallocSizeOf> ::malloc_size_of::MallocSizeOf for LabeledMetric<T> {
96 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
97 let map = self.label_map.lock().unwrap();
98
99 let shallow_size = if ops.has_malloc_enclosing_size_of() {
103 map.values()
104 .next()
105 .map_or(0, |v| unsafe { ops.malloc_enclosing_size_of(v) })
106 } else {
107 map.capacity()
108 * (mem::size_of::<String>() + mem::size_of::<T>() + mem::size_of::<usize>())
109 };
110
111 let mut map_size = shallow_size;
112 for (k, v) in map.iter() {
113 map_size += k.size_of(ops);
114 map_size += v.size_of(ops);
115 }
116
117 self.labels.size_of(ops) + self.submetric.size_of(ops) + map_size
118 }
119}
120
121mod private {
125 use super::LabeledMetricData;
126 use crate::metrics::{
127 BooleanMetric, CounterMetric, CustomDistributionMetric, MemoryDistributionMetric,
128 QuantityMetric, StringMetric, TimingDistributionMetric,
129 };
130
131 pub trait Sealed {
137 fn new_inner(meta: LabeledMetricData) -> Self;
139 }
140
141 impl Sealed for CounterMetric {
142 fn new_inner(meta: LabeledMetricData) -> Self {
143 match meta {
144 LabeledMetricData::Common { cmd } => Self::new(cmd),
145 _ => panic!("Incorrect construction of Labeled<CounterMetric>"),
146 }
147 }
148 }
149
150 impl Sealed for BooleanMetric {
151 fn new_inner(meta: LabeledMetricData) -> Self {
152 match meta {
153 LabeledMetricData::Common { cmd } => Self::new(cmd),
154 _ => panic!("Incorrect construction of Labeled<BooleanMetric>"),
155 }
156 }
157 }
158
159 impl Sealed for StringMetric {
160 fn new_inner(meta: LabeledMetricData) -> Self {
161 match meta {
162 LabeledMetricData::Common { cmd } => Self::new(cmd),
163 _ => panic!("Incorrect construction of Labeled<StringMetric>"),
164 }
165 }
166 }
167
168 impl Sealed for CustomDistributionMetric {
169 fn new_inner(meta: LabeledMetricData) -> Self {
170 match meta {
171 LabeledMetricData::CustomDistribution {
172 cmd,
173 range_min,
174 range_max,
175 bucket_count,
176 histogram_type,
177 } => Self::new(cmd, range_min, range_max, bucket_count, histogram_type),
178 _ => panic!("Incorrect construction of Labeled<CustomDistributionMetric>"),
179 }
180 }
181 }
182
183 impl Sealed for MemoryDistributionMetric {
184 fn new_inner(meta: LabeledMetricData) -> Self {
185 match meta {
186 LabeledMetricData::MemoryDistribution { cmd, unit } => Self::new(cmd, unit),
187 _ => panic!("Incorrect construction of Labeled<MemoryDistributionMetric>"),
188 }
189 }
190 }
191
192 impl Sealed for TimingDistributionMetric {
193 fn new_inner(meta: LabeledMetricData) -> Self {
194 match meta {
195 LabeledMetricData::TimingDistribution { cmd, unit } => Self::new(cmd, unit),
196 _ => panic!("Incorrect construction of Labeled<TimingDistributionMetric>"),
197 }
198 }
199 }
200
201 impl Sealed for QuantityMetric {
202 fn new_inner(meta: LabeledMetricData) -> Self {
203 match meta {
204 LabeledMetricData::Common { cmd } => Self::new(cmd),
205 _ => panic!("Incorrect construction of Labeled<QuantityMetric>"),
206 }
207 }
208 }
209}
210
211pub trait AllowLabeled: MetricType {
213 fn new_labeled(meta: LabeledMetricData) -> Self;
215}
216
217impl<T> AllowLabeled for T
219where
220 T: MetricType,
221 T: private::Sealed,
222{
223 fn new_labeled(meta: LabeledMetricData) -> Self {
224 T::new_inner(meta)
225 }
226}
227
228impl<T> LabeledMetric<T>
229where
230 T: AllowLabeled + Clone,
231{
232 pub fn new(
236 meta: LabeledMetricData,
237 labels: Option<Vec<Cow<'static, str>>>,
238 ) -> LabeledMetric<T> {
239 let submetric = T::new_labeled(meta);
240 LabeledMetric::new_inner(submetric, labels)
241 }
242
243 fn new_inner(submetric: T, labels: Option<Vec<Cow<'static, str>>>) -> LabeledMetric<T> {
244 let label_map = Default::default();
245 LabeledMetric {
246 labels,
247 submetric,
248 label_map,
249 }
250 }
251
252 fn new_metric_with_name(&self, name: String) -> T {
256 self.submetric.with_name(name)
257 }
258
259 fn new_metric_with_dynamic_label(&self, label: DynamicLabelType) -> T {
264 self.submetric.with_dynamic_label(label)
265 }
266
267 fn static_label<'a>(&self, label: &'a str) -> &'a str {
282 debug_assert!(self.labels.is_some());
283 let labels = self.labels.as_ref().unwrap();
284 if labels.iter().any(|l| l == label) {
285 label
286 } else {
287 OTHER_LABEL
288 }
289 }
290
291 pub fn get<S: AsRef<str>>(&self, label: S) -> Arc<T> {
303 let label = label.as_ref();
304
305 let id = format!("{}/{}", self.submetric.meta().base_identifier(), label);
308
309 let mut map = self.label_map.lock().unwrap();
310 match map.entry(id) {
311 Entry::Occupied(entry) => Arc::clone(entry.get()),
312 Entry::Vacant(entry) => {
313 let metric = match self.labels {
320 Some(_) => {
321 let label = self.static_label(label);
322 self.new_metric_with_name(combine_base_identifier_and_label(
323 &self.submetric.meta().inner.name,
324 label,
325 ))
326 }
327 None => self
328 .new_metric_with_dynamic_label(DynamicLabelType::Label(label.to_string())),
329 };
330 let metric = Arc::new(metric);
331 entry.insert(Arc::clone(&metric));
332 metric
333 }
334 }
335 }
336
337 pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
349 crate::block_on_dispatcher();
350 crate::core::with_glean(|glean| {
351 test_get_num_recorded_errors(glean, self.submetric.meta(), error).unwrap_or(0)
352 })
353 }
354}
355
356impl<T, S> TestGetValue<HashMap<String, S>> for LabeledMetric<T>
357where
358 T: AllowLabeled + TestGetValue<S>,
359 S: Any,
360{
361 fn test_get_value(&self, ping_name: Option<String>) -> Option<HashMap<String, S>> {
362 let mut out = HashMap::new();
363 let map = self.label_map.lock().unwrap();
364 map.iter().for_each(|(label, submetric)| {
365 if let Some(v) = submetric.test_get_value(ping_name.clone()) {
366 out.insert(
367 label.replace(&format!("{}/", self.submetric.meta().base_identifier()), ""),
368 v,
369 );
370 }
371 });
372 Some(out)
373 }
374}
375
376pub fn combine_base_identifier_and_label(base_identifer: &str, label: &str) -> String {
378 format!("{}/{}", base_identifer, label)
379}
380
381pub fn strip_label(identifier: &str) -> &str {
383 identifier.split_once('/').map_or(identifier, |s| s.0)
384}
385
386pub fn validate_dynamic_label(
400 glean: &Glean,
401 meta: &CommonMetricDataInternal,
402 base_identifier: &str,
403 label: &str,
404) -> String {
405 let key = combine_base_identifier_and_label(base_identifier, label);
406 for store in &meta.inner.send_in_pings {
407 if glean.storage().has_metric(meta.inner.lifetime, store, &key) {
408 return key;
409 }
410 }
411
412 let mut labels = HashSet::new();
413 let prefix = &key[..=base_identifier.len()];
414 let mut snapshotter = |metric_id: &[u8], _: &Metric| {
415 labels.insert(metric_id.to_vec());
416 };
417
418 let lifetime = meta.inner.lifetime;
419 for store in &meta.inner.send_in_pings {
420 glean
421 .storage()
422 .iter_store_from(lifetime, store, Some(prefix), &mut snapshotter);
423 }
424
425 let label_count = labels.len();
426 let error = if label_count >= MAX_LABELS {
427 true
428 } else if label.len() > MAX_LABEL_LENGTH {
429 let msg = format!(
430 "label length {} exceeds maximum of {}",
431 label.len(),
432 MAX_LABEL_LENGTH
433 );
434 record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
435 true
436 } else {
437 false
438 };
439
440 if error {
441 combine_base_identifier_and_label(base_identifier, OTHER_LABEL)
442 } else {
443 key
444 }
445}