use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
use crate::error_recording::{record_error, ErrorType};
use crate::metrics::Metric;
use crate::metrics::MetricType;
use crate::storage::StorageManager;
use crate::util::{truncate_string_at_boundary, truncate_string_at_boundary_with_error};
use crate::CommonMetricData;
use crate::Glean;
use crate::Lifetime;
const INTERNAL_STORAGE: &str = "glean_internal_info";
const MAX_EXPERIMENTS_IDS_LEN: usize = 100;
const MAX_EXPERIMENT_VALUE_LEN: usize = MAX_EXPERIMENTS_IDS_LEN;
const MAX_EXPERIMENTS_EXTRAS_SIZE: usize = 20;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RecordedExperimentData {
pub branch: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub extra: Option<HashMap<String, String>>,
}
#[derive(Clone, Debug)]
pub struct ExperimentMetric {
meta: CommonMetricData,
}
impl MetricType for ExperimentMetric {
fn meta(&self) -> &CommonMetricData {
&self.meta
}
fn meta_mut(&mut self) -> &mut CommonMetricData {
&mut self.meta
}
}
impl ExperimentMetric {
pub fn new(glean: &Glean, id: String) -> Self {
let mut error = None;
let truncated_id = if id.len() > MAX_EXPERIMENTS_IDS_LEN {
let msg = format!(
"Value length {} for experiment id exceeds maximum of {}",
id.len(),
MAX_EXPERIMENTS_IDS_LEN
);
error = Some(msg);
truncate_string_at_boundary(id, MAX_EXPERIMENTS_IDS_LEN)
} else {
id
};
let new_experiment = Self {
meta: CommonMetricData {
name: format!("{}#experiment", truncated_id),
category: "".into(),
send_in_pings: vec![INTERNAL_STORAGE.into()],
lifetime: Lifetime::Application,
..Default::default()
},
};
if let Some(msg) = error {
record_error(
glean,
&new_experiment.meta,
ErrorType::InvalidValue,
msg,
None,
);
}
new_experiment
}
pub fn set_active(
&self,
glean: &Glean,
branch: String,
extra: Option<HashMap<String, String>>,
) {
if !self.should_record(glean) {
return;
}
let truncated_branch = if branch.len() > MAX_EXPERIMENTS_IDS_LEN {
truncate_string_at_boundary_with_error(
glean,
&self.meta,
branch,
MAX_EXPERIMENTS_IDS_LEN,
)
} else {
branch
};
let truncated_extras = extra.and_then(|extra| {
if extra.len() > MAX_EXPERIMENTS_EXTRAS_SIZE {
let msg = format!(
"Extra hash map length {} exceeds maximum of {}",
extra.len(),
MAX_EXPERIMENTS_EXTRAS_SIZE
);
record_error(glean, &self.meta, ErrorType::InvalidValue, msg, None);
}
let mut temp_map = HashMap::new();
for (key, value) in extra.into_iter().take(MAX_EXPERIMENTS_EXTRAS_SIZE) {
let truncated_key = if key.len() > MAX_EXPERIMENTS_IDS_LEN {
truncate_string_at_boundary_with_error(
glean,
&self.meta,
key,
MAX_EXPERIMENTS_IDS_LEN,
)
} else {
key
};
let truncated_value = if value.len() > MAX_EXPERIMENT_VALUE_LEN {
truncate_string_at_boundary_with_error(
glean,
&self.meta,
value,
MAX_EXPERIMENT_VALUE_LEN,
)
} else {
value
};
temp_map.insert(truncated_key, truncated_value);
}
Some(temp_map)
});
let value = Metric::Experiment(RecordedExperimentData {
branch: truncated_branch,
extra: truncated_extras,
});
glean.storage().record(glean, &self.meta, &value)
}
pub fn set_inactive(&self, glean: &Glean) {
if !self.should_record(glean) {
return;
}
glean.storage().remove_single_metric(
Lifetime::Application,
INTERNAL_STORAGE,
&self.meta.name,
)
}
pub fn test_get_value_as_json_string(&self, glean: &Glean) -> Option<String> {
match StorageManager.snapshot_metric(
glean.storage(),
INTERNAL_STORAGE,
&self.meta.identifier(glean),
) {
Some(Metric::Experiment(e)) => Some(json!(e).to_string()),
_ => None,
}
}
}