/// Metrics that are defined in `metrics.yaml`, /// but are not used as "normal" metrics in code for various reasons. /// /// These will NOT be marked as errors. static DEFINITION_ONLY: &[&str] = &[ /* Adhoc used in code */ "glean.error.invalid_label", "glean.error.invalid_overflow", "glean.error.invalid_state", "glean.error.invalid_value", /* ping_info fields */ "glean.internal.metrics.start_time", "glean.internal.metrics.end_time", "glean.internal.metrics.experiments", "glean.internal.metrics.reason", "glean.internal.metrics.seq", /* hard-coded */ "glean.internal.metrics.telemetry_sdk_build", /* adhoc in src/ping/mod.rs */ "glean.ping.uploader_capabilities", /* adhoc event */ "glean.restarted", /* in foreign language wrapper */ "glean.validation.foreground_count",
];
/// `prefix "string" suffix` fn extract_string(line: &str) -> String { letmut parts = line.split('"');
parts
.next()
.unwrap_or_else(|| panic!("prefix before string opening missing in:\n{line}")); let result = parts
.next()
.unwrap_or_else(|| panic!("string in quotes missing in:\n{line}"));
parts
.next()
.unwrap_or_else(|| panic!("suffix after string closing missing in:\n{line}"));
result.to_string()
}
/// `lifetime: Lifetime::Ping` fn extract_lifetime(line: &str) -> String { let start = line
.find('L')
.unwrap_or_else(|| panic!("`Lifetime::` missing in\n{line}")); let end = line
.rfind(',')
.unwrap_or_else(|| panic!(", after Lifetime::* missing in\n{line}"));
line[start + "Lifetime::".len()..end].to_lowercase()
}
/// Extract definitions in code. fn extract_metrics_from_code(map: &mut HashMap<String, Metric>, file_path: &str) { let src =
fs::read_to_string(file_path).unwrap_or_else(|_| panic!("unable to read {file_path}"));
// We look for the line containing the `CommonMetricData {` opening. // We then enforce the ordering of additional fields: // - name // - category // - send_in_pings // - lifetime // // `name` before `category` because the majority of the code was already doing it that way. letmut lines = src.lines(); whilelet Some(line) = lines.next() { if line.contains("CommonMetricData ") { letmut metric = Metric::default(); let line = lines.next().unwrap();
assert!(line.contains("name:"));
metric.name = extract_string(line);
let line = lines.next().unwrap();
assert!(line.contains("category:"));
metric.category = extract_string(line);
// Special-casing some internals. if metric.category.is_empty() {
metric.category = String::from("glean.internal.metrics");
} if metric.category == "attribution" {
metric.category = String::from("glean.internal.metrics.attribution");
} if metric.category == "distribution" {
metric.category = String::from("glean.internal.metrics.distribution");
}
let line = lines.next().unwrap();
assert!(line.contains("send_in_pings:"));
metric.send_in_pings = extract_array(line);
metric.send_in_pings.sort();
let line = lines.next().unwrap();
assert!(line.contains("lifetime:"));
metric.lifetime = extract_lifetime(line);
let id = metric.id();
assert!(
map.insert(metric.id(), metric).is_none(), "duplicated metric in code: {id}"
);
}
}
}
/// Extract definitions in YAML. fn extract_metrics_from_yaml(map: &mut HashMap<String, Metric>, file_path: &str) { let metrics_definitions = fs::read_to_string(file_path).expect("unable to read metrics.yaml"); let docs = YamlLoader::load_from_str(&metrics_definitions).unwrap(); let docs = &docs[0];
for (category, metrics) in docs.as_hash().expect("need top-level to be a mapping") { let category = category.as_str().unwrap(); if category == "$schema" { continue;
}
for (metric_name, metric_definition) in
metrics.as_hash().expect("metric needs to be hashmap")
{ let metric_name = metric_name
.as_str()
.expect("metric name needs to be a string"); letmut metric = Metric {
category: category.to_string(),
name: metric_name.to_string(), // Defaulting to `ping` lifetime in case the definition doesn't set it
lifetime: String::from("ping"),
send_in_pings: vec![],
};
for (key, value) in metric_definition
.as_hash()
.expect("metric definition needs to be a hashmap")
{ let key = key.as_str().unwrap(); match key { "send_in_pings" => { let send_in_pings =
value.as_vec().expect("send_in_pings needs to be a list");
metric.send_in_pings = send_in_pings
.iter()
.map(|ping| {
ping.as_str()
.expect("ping in list needs to be a string")
.to_string()
})
.collect();
metric.send_in_pings.sort();
} "lifetime" => { let lifetime = value.as_str().expect("lifetime needs to be a string");
metric.lifetime = lifetime.to_string();
}
_ => {}
}
}
assert!(
map.insert(metric.id(), metric).is_none(), "duplicated metric defined"
);
}
}
}
// We check in both directions. // Only if at least one mismatch is found we fail the test (and provide a useful error message)
letmut mismatch_found = false; letmut msg = String::from("Not all metrics defined in code AND metrics.yaml\n\n");
msg.push_str("Defined in code, but not in metrics.yaml:\n"); letmut keys = metrics_in_code.keys().collect::<Vec<_>>();
keys.sort(); for key in keys.into_iter() { if !definitions.contains_key(key) {
msg.push_str(&format!("- {key}\n"));
mismatch_found = true;
}
}
msg.push_str("\nDefined in metrics.yaml, but not in code:\n"); letmut keys = definitions.keys().collect::<Vec<_>>();
keys.sort(); for key in keys.into_iter() { if DEFINITION_ONLY.contains(&&key[..]) { continue;
} if !metrics_in_code.contains_key(key) {
msg.push_str(&format!("- {key}\n"));
mismatch_found = true;
}
}
assert!(!mismatch_found, "{msg}");
// Double-checking lifetime & send_in_pings for code is the same as definition. for (id, metric) in metrics_in_code { let Some(defined_metric) = definitions.get(&id) else { continue;
};
assert_eq!(&metric, defined_metric);
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-08-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.