use std::fmt::Display;
use std::sync::atomic::AtomicU8;
use rusqlite::Transaction;
use crate::common_metric_data::CommonMetricDataInternal;
use crate::error::{Error, ErrorKind};
use crate::metrics::{CounterMetric, Metric};
use crate::Glean;
use crate::Lifetime;
use crate::{CommonMetricData, MetricLabel};
#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ErrorType {
InvalidValue,
InvalidLabel,
InvalidState,
InvalidOverflow,
}
impl ErrorType {
pub fn as_str(&self) -> &'static str {
match self {
ErrorType::InvalidValue => "invalid_value",
ErrorType::InvalidLabel => "invalid_label",
ErrorType::InvalidState => "invalid_state",
ErrorType::InvalidOverflow => "invalid_overflow",
}
}
pub fn iter() -> impl Iterator<Item = Self> {
[
ErrorType::InvalidValue,
ErrorType::InvalidLabel,
ErrorType::InvalidState,
ErrorType::InvalidOverflow,
]
.iter()
.copied()
}
}
impl TryFrom<i32> for ErrorType {
type Error = Error;
fn try_from(value: i32) -> Result<ErrorType, Self::Error> {
match value {
0 => Ok(ErrorType::InvalidValue),
1 => Ok(ErrorType::InvalidLabel),
2 => Ok(ErrorType::InvalidState),
3 => Ok(ErrorType::InvalidOverflow),
e => Err(ErrorKind::Lifetime(e).into()),
}
}
}
fn get_error_metric_for_metric(meta: &CommonMetricDataInternal, error: ErrorType) -> CounterMetric {
let name = meta.base_identifier();
let mut send_in_pings = meta.inner.send_in_pings.clone();
let ping_name = "metrics".to_string();
if !send_in_pings.contains(&ping_name) {
send_in_pings.push(ping_name);
}
CounterMetric::new(CommonMetricData {
name: error.as_str().to_string(),
category: "glean.error".into(),
lifetime: Lifetime::Ping,
send_in_pings,
label: Some(MetricLabel::Label(name.to_string())),
..Default::default()
})
}
pub fn record_error<O: Into<Option<i32>>>(
glean: &Glean,
meta: &CommonMetricDataInternal,
error: ErrorType,
message: impl Display,
num_errors: O,
) {
let metric = get_error_metric_for_metric(meta, error);
log::warn!("{}: {}", meta.base_identifier(), message);
let to_report = num_errors.into().unwrap_or(1);
debug_assert!(to_report > 0);
metric.add_sync(glean, to_report);
}
pub fn record_error_sqlite(
glean: &Glean,
tx: &mut Transaction,
metric_name: &str,
send_in_pings: &[String],
error: ErrorType,
num_errors: i32,
) {
debug_assert!(num_errors > 0);
if num_errors <= 0 {
log::warn!("Trying to record {num_errors} errors for {metric_name:?} (<= 0). Bailing out.");
return;
}
let ping_name = String::from("metrics");
let mut send_in_pings = send_in_pings.to_vec();
if !send_in_pings.contains(&ping_name) {
send_in_pings.push(ping_name);
}
let lifetime = Lifetime::Ping;
let transform = |old_value| match old_value {
Some(Metric::Counter(old_value)) => Metric::Counter(old_value.saturating_add(num_errors)),
_ => Metric::Counter(num_errors),
};
let inner = CommonMetricData {
category: String::from("glean.error"),
name: String::from(error.as_str()),
send_in_pings,
lifetime,
label: Some(MetricLabel::Static(String::from(metric_name))),
..Default::default()
};
let cmd = CommonMetricDataInternal {
inner,
disabled: AtomicU8::new(0),
};
_ = glean
.storage()
.record_with_transaction(glean, tx, &cmd, transform);
}
pub fn test_get_num_recorded_errors(
glean: &Glean,
meta: &CommonMetricDataInternal,
error: ErrorType,
) -> Result<i32, String> {
let metric = get_error_metric_for_metric(meta, error);
metric.get_value(glean, Some("metrics")).ok_or_else(|| {
format!(
"No error recorded for {} in 'metrics' store",
meta.base_identifier(),
)
})
}
#[cfg(test)]
mod test {
use super::*;
use crate::metrics::*;
use crate::tests::new_glean;
#[test]
fn error_type_i32_mapping() {
let error: ErrorType = std::convert::TryFrom::try_from(0).unwrap();
assert_eq!(error, ErrorType::InvalidValue);
let error: ErrorType = std::convert::TryFrom::try_from(1).unwrap();
assert_eq!(error, ErrorType::InvalidLabel);
let error: ErrorType = std::convert::TryFrom::try_from(2).unwrap();
assert_eq!(error, ErrorType::InvalidState);
let error: ErrorType = std::convert::TryFrom::try_from(3).unwrap();
assert_eq!(error, ErrorType::InvalidOverflow);
}
#[test]
fn recording_of_all_error_types() {
let (glean, _t) = new_glean(None);
let string_metric = StringMetric::new(CommonMetricData {
name: "string_metric".into(),
category: "telemetry".into(),
send_in_pings: vec!["store1".into(), "store2".into()],
disabled: false,
lifetime: Lifetime::User,
..Default::default()
});
let expected_invalid_values_errors: i32 = 1;
let expected_invalid_labels_errors: i32 = 2;
record_error(
&glean,
string_metric.meta(),
ErrorType::InvalidValue,
"Invalid value",
None,
);
record_error(
&glean,
string_metric.meta(),
ErrorType::InvalidLabel,
"Invalid label",
expected_invalid_labels_errors,
);
let invalid_val =
get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidValue);
let invalid_label =
get_error_metric_for_metric(string_metric.meta(), ErrorType::InvalidLabel);
for &store in &["store1", "store2", "metrics"] {
assert_eq!(
Some(expected_invalid_values_errors),
invalid_val.get_value(&glean, Some(store))
);
assert_eq!(
Some(expected_invalid_labels_errors),
invalid_label.get_value(&glean, Some(store))
);
}
}
}