use lazy_static::lazy_static;
use regex::Regex;
use crate::common_metric_data::CommonMetricData;
use crate::error_recording::{record_error, ErrorType};
use crate::metrics::{Metric, MetricType};
use crate::Glean;
const MAX_LABELS: usize = 16;
const OTHER_LABEL: &str = "__other__";
const MAX_LABEL_LENGTH: usize = 61;
lazy_static! {
static ref LABEL_REGEX: Regex = Regex::new("^[a-z_][a-z0-9_-]{0,29}(\\.[a-z0-9_-]{0,29})*$").unwrap();
}
#[derive(Clone, Debug)]
pub struct LabeledMetric<T> {
labels: Option<Vec<String>>,
submetric: T,
}
impl<T> LabeledMetric<T>
where
T: MetricType + Clone,
{
pub fn new(submetric: T, labels: Option<Vec<String>>) -> LabeledMetric<T> {
LabeledMetric { labels, submetric }
}
fn new_metric_with_name(&self, name: String) -> T {
let mut t = self.submetric.clone();
t.meta_mut().name = name;
t
}
fn new_metric_with_dynamic_label(&self, label: String) -> T {
let mut t = self.submetric.clone();
t.meta_mut().dynamic_label = Some(label);
t
}
fn static_label<'a>(&mut self, label: &'a str) -> &'a str {
debug_assert!(self.labels.is_some());
let labels = self.labels.as_ref().unwrap();
if labels.iter().any(|l| l == label) {
label
} else {
OTHER_LABEL
}
}
pub fn get(&mut self, label: &str) -> T {
match self.labels {
Some(_) => {
let label = self.static_label(label);
self.new_metric_with_name(combine_base_identifier_and_label(
&self.submetric.meta().name,
&label,
))
}
None => self.new_metric_with_dynamic_label(label.to_string()),
}
}
pub fn get_submetric(&self) -> &T {
&self.submetric
}
}
pub fn combine_base_identifier_and_label(base_identifer: &str, label: &str) -> String {
format!("{}/{}", base_identifer, label)
}
pub fn strip_label(identifier: &str) -> &str {
identifier.splitn(2, '/').next().unwrap()
}
pub fn dynamic_label(
glean: &Glean,
meta: &CommonMetricData,
base_identifier: &str,
label: &str,
) -> String {
let key = combine_base_identifier_and_label(base_identifier, label);
for store in &meta.send_in_pings {
if glean.storage().has_metric(meta.lifetime, store, &key) {
return key;
}
}
let mut label_count = 0;
let prefix = &key[..=base_identifier.len()];
let mut snapshotter = |_: &[u8], _: &Metric| {
label_count += 1;
};
let lifetime = meta.lifetime;
for store in &meta.send_in_pings {
glean
.storage()
.iter_store_from(lifetime, store, Some(&prefix), &mut snapshotter);
}
let error = if label_count >= MAX_LABELS {
true
} else if label.len() > MAX_LABEL_LENGTH {
let msg = format!(
"label length {} exceeds maximum of {}",
label.len(),
MAX_LABEL_LENGTH
);
record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
true
} else if !LABEL_REGEX.is_match(label) {
let msg = format!("label must be snake_case, got '{}'", label);
record_error(glean, meta, ErrorType::InvalidLabel, msg, None);
true
} else {
false
};
if error {
combine_base_identifier_and_label(base_identifier, OTHER_LABEL)
} else {
key
}
}