Adding a new metric type

Data in the Glean SDK is stored in so-called metrics. You can find the full list of implemented metric types in the user overview.

Adding a new metric type involves defining the metric type's API, its persisted and in-memory storage as well as its serialization into the ping payload.

The metric type's API

A metric type implementation is defined in its own file under glean-core/src/metrics/, e.g. glean-core/src/metrics/counter.rs for a Counter.

Start by defining a structure to hold the metric's metadata:

#[derive(Clone, Debug)]
pub struct CounterMetric {
    meta: CommonMetricData
}

Implement the MetricType trait to create a metric from the meta data as well as expose the meta data. This also gives you a should_record method on the metric type.

impl MetricType for CounterMetric {
    fn meta(&self) -> &CommonMetricData {
        &self.meta
    }

    fn meta_mut(&mut self) -> &mut CommonMetricData {
        &mut self.meta
    }
}

Its implementation should have a way to create a new metric from the common metric data. It should be the same for all metric types.

impl CounterMetric {
    pub fn new(meta: CommonMetricData) -> Self {
        Self { meta }
    }
}

Implement each method for the type. The first argument to accept should always be glean: &Glean, that is: a reference to the Glean object, used to access the storage:

impl CounterMetric { // same block as above
    pub fn add(&self, glean: &Glean, amount: i32) {
        // Always include this check!
        if !self.should_record() {
            return;
        }

        // Do error handling here

        glean
            .storage()
            .record_with(&self.meta, |old_value| match old_value {
                Some(Metric::Counter(old_value)) => Metric::Counter(old_value + amount),
                _ => Metric::Counter(amount),
            })
    }
}

Use glean.storage().record() to record a fixed value or glean.storage.record_with() to construct a new value from the currently stored one.

The storage operation makes use of the metric's variant of the Metric enumeration.

The Metric enumeration

Persistence and in-memory serialization as well as ping payload serialization are handled through the Metric enumeration. This is defined in glean-core/src/metrics/mod.rs. Variants of this enumeration are used in the storage implementation of the metric type.

To add a new metric type, include the metric module and declare its use, then add a new variant to the Metric enum:


mod counter;

// ...

pub use self::counter::CounterMetric;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Metric {
    // ...
    Counter(i32),
}

Then modify the below implementation and define the right ping section name for the new type. This will be used in the ping payload:

impl Metric {
    pub fn ping_section(&self) -> &'static str {
        match self {
            // ...
            Metric::Counter(_) => "counter",
        }
    }
}

Finally, define the ping payload serialization (as JSON). In the simple cases where the in-memory representation maps to its JSON representation it is enough to call the json! macro.

impl Metric { // same block as above
    pub fn as_json(&self) -> JsonValue {
        match self {
            // ...
            Metric::Counter(c) => json!(c),
        }
    }
}

For more complex serialization consider implementing serialization logic as a function returning a serde_json::Value or another object that can be serialized.

For example, the DateTime serializer has the following entry, where get_iso_time_string is a function to convert from the DateTime metric representation to a string:

Metric::Datetime(d, time_unit) => json!(get_iso_time_string(*d, *time_unit)),

In the next step we will create the FFI wrapper and platform-specific wrappers.