summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/api/src/private/labeled.rs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/glean/api/src/private/labeled.rs')
-rw-r--r--toolkit/components/glean/api/src/private/labeled.rs356
1 files changed, 356 insertions, 0 deletions
diff --git a/toolkit/components/glean/api/src/private/labeled.rs b/toolkit/components/glean/api/src/private/labeled.rs
new file mode 100644
index 0000000000..b0bedafa15
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/labeled.rs
@@ -0,0 +1,356 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use inherent::inherent;
+
+use super::{
+ CommonMetricData, ErrorType, LabeledBooleanMetric, LabeledCounterMetric, LabeledStringMetric,
+ MetricId,
+};
+use crate::ipc::need_ipc;
+
+/// Sealed traits protect against downstream implementations.
+///
+/// We wrap it in a private module that is inaccessible outside of this module.
+mod private {
+ use super::{
+ need_ipc, LabeledBooleanMetric, LabeledCounterMetric, LabeledStringMetric, MetricId,
+ };
+ use crate::private::CounterMetric;
+ use std::sync::Arc;
+
+ /// The sealed trait.
+ ///
+ /// This allows us to define which FOG metrics can be used
+ /// as labeled types.
+ pub trait Sealed {
+ type GleanMetric: glean::private::AllowLabeled + Clone;
+ fn from_glean_metric(id: MetricId, metric: Arc<Self::GleanMetric>, label: &str) -> Self;
+ }
+
+ // `LabeledMetric<LabeledBooleanMetric>` is possible.
+ //
+ // See [Labeled Booleans](https://mozilla.github.io/glean/book/user/metrics/labeled_booleans.html).
+ impl Sealed for LabeledBooleanMetric {
+ type GleanMetric = glean::private::BooleanMetric;
+ fn from_glean_metric(_id: MetricId, metric: Arc<Self::GleanMetric>, _label: &str) -> Self {
+ if need_ipc() {
+ // TODO: Instrument this error.
+ LabeledBooleanMetric::Child(crate::private::boolean::BooleanMetricIpc)
+ } else {
+ LabeledBooleanMetric::Parent(metric)
+ }
+ }
+ }
+
+ // `LabeledMetric<LabeledStringMetric>` is possible.
+ //
+ // See [Labeled Strings](https://mozilla.github.io/glean/book/user/metrics/labeled_strings.html).
+ impl Sealed for LabeledStringMetric {
+ type GleanMetric = glean::private::StringMetric;
+ fn from_glean_metric(_id: MetricId, metric: Arc<Self::GleanMetric>, _label: &str) -> Self {
+ if need_ipc() {
+ // TODO: Instrument this error.
+ LabeledStringMetric::Child(crate::private::string::StringMetricIpc)
+ } else {
+ LabeledStringMetric::Parent(metric)
+ }
+ }
+ }
+
+ // `LabeledMetric<LabeledCounterMetric>` is possible.
+ //
+ // See [Labeled Counters](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html).
+ impl Sealed for LabeledCounterMetric {
+ type GleanMetric = glean::private::CounterMetric;
+ fn from_glean_metric(id: MetricId, metric: Arc<Self::GleanMetric>, label: &str) -> Self {
+ if need_ipc() {
+ LabeledCounterMetric::Child {
+ id,
+ label: label.to_string(),
+ }
+ } else {
+ LabeledCounterMetric::Parent(CounterMetric::Parent { id, inner: metric })
+ }
+ }
+ }
+}
+
+/// Marker trait for metrics that can be nested inside a labeled metric.
+///
+/// This trait is sealed and cannot be implemented for types outside this crate.
+pub trait AllowLabeled: private::Sealed {}
+
+// Implement the trait for everything we marked as allowed.
+impl<T> AllowLabeled for T where T: private::Sealed {}
+
+/// A labeled metric.
+///
+/// Labeled metrics allow to record multiple sub-metrics of the same type under different string labels.
+///
+/// ## Example
+///
+/// The following piece of code will be generated by `glean_parser`:
+///
+/// ```rust,ignore
+/// use glean::metrics::{LabeledMetric, BooleanMetric, CommonMetricData, Lifetime};
+/// use once_cell::sync::Lazy;
+///
+/// mod error {
+/// pub static seen_one: Lazy<LabeledMetric<BooleanMetric>> = Lazy::new(|| LabeledMetric::new(CommonMetricData {
+/// name: "seen_one".into(),
+/// category: "error".into(),
+/// send_in_pings: vec!["ping".into()],
+/// disabled: false,
+/// lifetime: Lifetime::Ping,
+/// ..Default::default()
+/// }, None));
+/// }
+/// ```
+///
+/// It can then be used with:
+///
+/// ```rust,ignore
+/// errro::seen_one.get("upload").set(true);
+/// ```
+pub struct LabeledMetric<T: AllowLabeled> {
+ /// The metric ID of the underlying metric.
+ id: MetricId,
+
+ /// Wrapping the underlying core metric.
+ ///
+ /// We delegate all functionality to this and wrap it up again in our own metric type.
+ core: glean::private::LabeledMetric<T::GleanMetric>,
+}
+
+impl<T> LabeledMetric<T>
+where
+ T: AllowLabeled,
+{
+ /// Create a new labeled metric from the given metric instance and optional list of labels.
+ ///
+ /// See [`get`](#method.get) for information on how static or dynamic labels are handled.
+ pub fn new(
+ id: MetricId,
+ meta: CommonMetricData,
+ labels: Option<Vec<String>>,
+ ) -> LabeledMetric<T> {
+ let core = glean::private::LabeledMetric::new(meta, labels);
+ LabeledMetric { id, core }
+ }
+}
+
+#[inherent]
+impl<U> glean::traits::Labeled<U> for LabeledMetric<U>
+where
+ U: AllowLabeled + Clone,
+{
+ /// Gets a specific metric for a given label.
+ ///
+ /// If a set of acceptable labels were specified in the `metrics.yaml` file,
+ /// and the given label is not in the set, it will be recorded under the special `OTHER_LABEL` label.
+ ///
+ /// If a set of acceptable labels was not specified in the `metrics.yaml` file,
+ /// only the first 16 unique labels will be used.
+ /// After that, any additional labels will be recorded under the special `OTHER_LABEL` label.
+ ///
+ /// Labels must be `snake_case` and less than 30 characters.
+ /// If an invalid label is used, the metric will be recorded in the special `OTHER_LABEL` label.
+ pub fn get(&self, label: &str) -> U {
+ let metric = self.core.get(label);
+ U::from_glean_metric(self.id, metric, label)
+ }
+
+ /// **Exported for test purposes.**
+ ///
+ /// Gets the number of recorded errors for the given metric and error type.
+ ///
+ /// # Arguments
+ ///
+ /// * `error` - The type of error
+ /// * `ping_name` - represents the optional name of the ping to retrieve the
+ /// metric for. Defaults to the first value in `send_in_pings`.
+ ///
+ /// # Returns
+ ///
+ /// The number of errors reported.
+ pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
+ if need_ipc() {
+ panic!("Use of labeled metrics in IPC land not yet implemented!");
+ } else {
+ self.core.test_get_num_recorded_errors(error)
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use once_cell::sync::Lazy;
+
+ use super::*;
+ use crate::common_test::*;
+
+ // Smoke test for what should be the generated code.
+ static GLOBAL_METRIC: Lazy<LabeledMetric<LabeledBooleanMetric>> = Lazy::new(|| {
+ LabeledMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "global".into(),
+ category: "metric".into(),
+ send_in_pings: vec!["ping".into()],
+ disabled: false,
+ ..Default::default()
+ },
+ None,
+ )
+ });
+
+ #[test]
+ fn smoke_test_global_metric() {
+ let _lock = lock_test();
+
+ GLOBAL_METRIC.get("a_value").set(true);
+ assert_eq!(
+ true,
+ GLOBAL_METRIC.get("a_value").test_get_value("ping").unwrap()
+ );
+ }
+
+ #[test]
+ fn sets_labeled_bool_metrics() {
+ let _lock = lock_test();
+ let store_names: Vec<String> = vec!["store1".into()];
+
+ let metric: LabeledMetric<LabeledBooleanMetric> = LabeledMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "bool".into(),
+ category: "labeled".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ ..Default::default()
+ },
+ None,
+ );
+
+ metric.get("upload").set(true);
+
+ assert!(metric.get("upload").test_get_value("store1").unwrap());
+ assert_eq!(None, metric.get("download").test_get_value("store1"));
+ }
+
+ #[test]
+ fn sets_labeled_string_metrics() {
+ let _lock = lock_test();
+ let store_names: Vec<String> = vec!["store1".into()];
+
+ let metric: LabeledMetric<LabeledStringMetric> = LabeledMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "string".into(),
+ category: "labeled".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ ..Default::default()
+ },
+ None,
+ );
+
+ metric.get("upload").set("Glean");
+
+ assert_eq!(
+ "Glean",
+ metric.get("upload").test_get_value("store1").unwrap()
+ );
+ assert_eq!(None, metric.get("download").test_get_value("store1"));
+ }
+
+ #[test]
+ fn sets_labeled_counter_metrics() {
+ let _lock = lock_test();
+ let store_names: Vec<String> = vec!["store1".into()];
+
+ let metric: LabeledMetric<LabeledCounterMetric> = LabeledMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "counter".into(),
+ category: "labeled".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ ..Default::default()
+ },
+ None,
+ );
+
+ metric.get("upload").add(10);
+
+ assert_eq!(10, metric.get("upload").test_get_value("store1").unwrap());
+ assert_eq!(None, metric.get("download").test_get_value("store1"));
+ }
+
+ #[test]
+ fn records_errors() {
+ let _lock = lock_test();
+ let store_names: Vec<String> = vec!["store1".into()];
+
+ let metric: LabeledMetric<LabeledBooleanMetric> = LabeledMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "bool".into(),
+ category: "labeled".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ ..Default::default()
+ },
+ None,
+ );
+
+ metric
+ .get("this_string_has_more_than_thirty_characters")
+ .set(true);
+
+ assert_eq!(
+ 1,
+ metric.test_get_num_recorded_errors(ErrorType::InvalidLabel)
+ );
+ }
+
+ #[test]
+ fn predefined_labels() {
+ let _lock = lock_test();
+ let store_names: Vec<String> = vec!["store1".into()];
+
+ let metric: LabeledMetric<LabeledBooleanMetric> = LabeledMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "bool".into(),
+ category: "labeled".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ ..Default::default()
+ },
+ Some(vec!["label1".into(), "label2".into()]),
+ );
+
+ metric.get("label1").set(true);
+ metric.get("label2").set(false);
+ metric.get("not_a_label").set(true);
+
+ assert_eq!(true, metric.get("label1").test_get_value("store1").unwrap());
+ assert_eq!(
+ false,
+ metric.get("label2").test_get_value("store1").unwrap()
+ );
+ // The label not in the predefined set is recorded to the `other` bucket.
+ assert_eq!(
+ true,
+ metric.get("__other__").test_get_value("store1").unwrap()
+ );
+
+ assert_eq!(
+ 0,
+ metric.test_get_num_recorded_errors(ErrorType::InvalidLabel)
+ );
+ }
+}