glean_core/metrics/
mod.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! The different metric types supported by the Glean SDK to handle data.
6
7use std::collections::HashMap;
8use std::sync::atomic::Ordering;
9
10use chrono::{DateTime, FixedOffset};
11use serde::{Deserialize, Serialize};
12use serde_json::json;
13pub use serde_json::Value as JsonValue;
14
15mod boolean;
16mod counter;
17mod custom_distribution;
18mod datetime;
19mod denominator;
20mod event;
21mod experiment;
22pub(crate) mod labeled;
23mod memory_distribution;
24mod memory_unit;
25mod numerator;
26mod object;
27mod ping;
28mod quantity;
29mod rate;
30mod recorded_experiment;
31mod remote_settings_config;
32mod string;
33mod string_list;
34mod text;
35mod time_unit;
36mod timespan;
37mod timing_distribution;
38mod url;
39mod uuid;
40
41use crate::common_metric_data::CommonMetricDataInternal;
42pub use crate::event_database::RecordedEvent;
43use crate::histogram::{Functional, Histogram, PrecomputedExponential, PrecomputedLinear};
44pub use crate::metrics::datetime::Datetime;
45use crate::util::get_iso_time_string;
46use crate::Glean;
47
48pub use self::boolean::BooleanMetric;
49pub use self::counter::CounterMetric;
50pub use self::custom_distribution::{CustomDistributionMetric, LocalCustomDistribution};
51pub use self::datetime::DatetimeMetric;
52pub use self::denominator::DenominatorMetric;
53pub use self::event::EventMetric;
54pub(crate) use self::experiment::ExperimentMetric;
55pub use self::labeled::{
56    LabeledBoolean, LabeledCounter, LabeledCustomDistribution, LabeledMemoryDistribution,
57    LabeledMetric, LabeledQuantity, LabeledString, LabeledTimingDistribution,
58};
59pub use self::memory_distribution::{LocalMemoryDistribution, MemoryDistributionMetric};
60pub use self::memory_unit::MemoryUnit;
61pub use self::numerator::NumeratorMetric;
62pub use self::object::ObjectMetric;
63pub use self::ping::PingType;
64pub use self::quantity::QuantityMetric;
65pub use self::rate::{Rate, RateMetric};
66pub use self::string::StringMetric;
67pub use self::string_list::StringListMetric;
68pub use self::text::TextMetric;
69pub use self::time_unit::TimeUnit;
70pub use self::timespan::TimespanMetric;
71pub use self::timing_distribution::LocalTimingDistribution;
72pub use self::timing_distribution::TimerId;
73pub use self::timing_distribution::TimingDistributionMetric;
74pub use self::url::UrlMetric;
75pub use self::uuid::UuidMetric;
76pub use crate::histogram::HistogramType;
77pub use recorded_experiment::RecordedExperiment;
78
79pub use self::remote_settings_config::RemoteSettingsConfig;
80
81/// A snapshot of all buckets and the accumulated sum of a distribution.
82//
83// Note: Be careful when changing this structure.
84// The serialized form ends up in the ping payload.
85// New fields might require to be skipped on serialization.
86#[derive(Debug, Serialize, PartialEq)]
87pub struct DistributionData {
88    /// A map containig the bucket index mapped to the accumulated count.
89    ///
90    /// This can contain buckets with a count of `0`.
91    pub values: HashMap<i64, i64>,
92
93    /// The accumulated sum of all the samples in the distribution.
94    pub sum: i64,
95
96    /// The total number of entries in the distribution.
97    #[serde(skip)]
98    pub count: i64,
99}
100
101/// The available metrics.
102///
103/// This is the in-memory and persisted layout of a metric.
104///
105/// ## Note
106///
107/// The order of metrics in this enum is important, as it is used for serialization.
108/// Do not reorder the variants.
109///
110/// **Any new metric must be added at the end.**
111#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
112pub enum Metric {
113    /// A boolean metric. See [`BooleanMetric`] for more information.
114    Boolean(bool),
115    /// A counter metric. See [`CounterMetric`] for more information.
116    Counter(i32),
117    /// A custom distribution with precomputed exponential bucketing.
118    /// See [`CustomDistributionMetric`] for more information.
119    CustomDistributionExponential(Histogram<PrecomputedExponential>),
120    /// A custom distribution with precomputed linear bucketing.
121    /// See [`CustomDistributionMetric`] for more information.
122    CustomDistributionLinear(Histogram<PrecomputedLinear>),
123    /// A datetime metric. See [`DatetimeMetric`] for more information.
124    Datetime(DateTime<FixedOffset>, TimeUnit),
125    /// An experiment metric. See `ExperimentMetric` for more information.
126    Experiment(recorded_experiment::RecordedExperiment),
127    /// A quantity metric. See [`QuantityMetric`] for more information.
128    Quantity(i64),
129    /// A string metric. See [`StringMetric`] for more information.
130    String(String),
131    /// A string list metric. See [`StringListMetric`] for more information.
132    StringList(Vec<String>),
133    /// A UUID metric. See [`UuidMetric`] for more information.
134    Uuid(String),
135    /// A timespan metric. See [`TimespanMetric`] for more information.
136    Timespan(std::time::Duration, TimeUnit),
137    /// A timing distribution. See [`TimingDistributionMetric`] for more information.
138    TimingDistribution(Histogram<Functional>),
139    /// A memory distribution. See [`MemoryDistributionMetric`] for more information.
140    MemoryDistribution(Histogram<Functional>),
141    /// **DEPRECATED**: A JWE metric..
142    /// Note: This variant MUST NOT be removed to avoid backwards-incompatible changes to the
143    /// serialization. This type has no underlying implementation anymore.
144    Jwe(String),
145    /// A rate metric. See [`RateMetric`] for more information.
146    Rate(i32, i32),
147    /// A URL metric. See [`UrlMetric`] for more information.
148    Url(String),
149    /// A Text metric. See [`TextMetric`] for more information.
150    Text(String),
151    /// An Object metric. See [`ObjectMetric`] for more information.
152    Object(String),
153}
154
155/// A [`MetricType`] describes common behavior across all metrics.
156pub trait MetricType {
157    /// Access the stored metadata
158    fn meta(&self) -> &CommonMetricDataInternal;
159
160    /// Create a new metric from this with a new name.
161    fn with_name(&self, _name: String) -> Self
162    where
163        Self: Sized,
164    {
165        unimplemented!()
166    }
167
168    /// Create a new metric from this with a specific label.
169    fn with_dynamic_label(&self, _label: String) -> Self
170    where
171        Self: Sized,
172    {
173        unimplemented!()
174    }
175
176    /// Whether this metric should currently be recorded
177    ///
178    /// This depends on the metrics own state, as determined by its metadata,
179    /// and whether upload is enabled on the Glean object.
180    fn should_record(&self, glean: &Glean) -> bool {
181        // Technically nothing prevents multiple calls to should_record() to run in parallel,
182        // meaning both are reading self.meta().disabled and later writing it. In between it can
183        // also read remote_settings_config, which also could be modified in between those 2 reads.
184        // This means we could write the wrong remote_settings_epoch | current_disabled value. All in all
185        // at worst we would see that metric enabled/disabled wrongly once.
186        // But since everything is tunneled through the dispatcher, this should never ever happen.
187
188        // Get the current disabled field from the metric metadata, including
189        // the encoded remote_settings epoch
190        let disabled_field = self.meta().disabled.load(Ordering::Relaxed);
191        // Grab the epoch from the upper nibble
192        let epoch = disabled_field >> 4;
193        // Get the disabled flag from the lower nibble
194        let disabled = disabled_field & 0xF;
195        // Get the current remote_settings epoch to see if we need to bother with the
196        // more expensive HashMap lookup
197        let remote_settings_epoch = glean.remote_settings_epoch.load(Ordering::Acquire);
198        if epoch == remote_settings_epoch {
199            return disabled == 0;
200        }
201        // The epoch's didn't match so we need to look up the disabled flag
202        // by the base_identifier from the in-memory HashMap
203        let remote_settings_config = &glean.remote_settings_config.lock().unwrap();
204        // Get the value from the remote configuration if it is there, otherwise return the default value.
205        let current_disabled = {
206            let base_id = self.meta().base_identifier();
207            let identifier = base_id
208                .split_once('/')
209                .map(|split| split.0)
210                .unwrap_or(&base_id);
211            // NOTE: The `!` preceding the `*is_enabled` is important for inverting the logic since the
212            // underlying property in the metrics.yaml is `disabled` and the outward API is treating it as
213            // if it were `enabled` to make it easier to understand.
214
215            if !remote_settings_config.metrics_enabled.is_empty() {
216                if let Some(is_enabled) = remote_settings_config.metrics_enabled.get(identifier) {
217                    u8::from(!*is_enabled)
218                } else {
219                    u8::from(self.meta().inner.disabled)
220                }
221            } else {
222                u8::from(self.meta().inner.disabled)
223            }
224        };
225
226        // Re-encode the epoch and enabled status and update the metadata
227        let new_disabled = (remote_settings_epoch << 4) | (current_disabled & 0xF);
228        self.meta().disabled.store(new_disabled, Ordering::Relaxed);
229
230        // Return a boolean indicating whether or not the metric should be recorded
231        current_disabled == 0
232    }
233}
234
235/// A [`MetricIdentifier`] describes an interface for retrieving an
236/// identifier (category, name, label) for a metric
237pub trait MetricIdentifier<'a> {
238    /// Retrieve the category, name and (maybe) label of the metric
239    fn get_identifiers(&'a self) -> (&'a str, &'a str, Option<&'a str>);
240}
241
242// Provide a blanket implementation for MetricIdentifier for all the types
243// that implement MetricType.
244impl<'a, T> MetricIdentifier<'a> for T
245where
246    T: MetricType,
247{
248    fn get_identifiers(&'a self) -> (&'a str, &'a str, Option<&'a str>) {
249        let meta = &self.meta().inner;
250        (&meta.category, &meta.name, meta.dynamic_label.as_deref())
251    }
252}
253
254impl Metric {
255    /// Gets the ping section the metric fits into.
256    ///
257    /// This determines the section of the ping to place the metric data in when
258    /// assembling the ping payload.
259    pub fn ping_section(&self) -> &'static str {
260        match self {
261            Metric::Boolean(_) => "boolean",
262            Metric::Counter(_) => "counter",
263            // Custom distributions are in the same section, no matter what bucketing.
264            Metric::CustomDistributionExponential(_) => "custom_distribution",
265            Metric::CustomDistributionLinear(_) => "custom_distribution",
266            Metric::Datetime(_, _) => "datetime",
267            Metric::Experiment(_) => panic!("Experiments should not be serialized through this"),
268            Metric::Quantity(_) => "quantity",
269            Metric::Rate(..) => "rate",
270            Metric::String(_) => "string",
271            Metric::StringList(_) => "string_list",
272            Metric::Timespan(..) => "timespan",
273            Metric::TimingDistribution(_) => "timing_distribution",
274            Metric::Url(_) => "url",
275            Metric::Uuid(_) => "uuid",
276            Metric::MemoryDistribution(_) => "memory_distribution",
277            Metric::Jwe(_) => "jwe",
278            Metric::Text(_) => "text",
279            Metric::Object(_) => "object",
280        }
281    }
282
283    /// The JSON representation of the metric's data
284    pub fn as_json(&self) -> JsonValue {
285        match self {
286            Metric::Boolean(b) => json!(b),
287            Metric::Counter(c) => json!(c),
288            Metric::CustomDistributionExponential(hist) => {
289                json!(custom_distribution::snapshot(hist))
290            }
291            Metric::CustomDistributionLinear(hist) => json!(custom_distribution::snapshot(hist)),
292            Metric::Datetime(d, time_unit) => json!(get_iso_time_string(*d, *time_unit)),
293            Metric::Experiment(e) => e.as_json(),
294            Metric::Quantity(q) => json!(q),
295            Metric::Rate(num, den) => {
296                json!({"numerator": num, "denominator": den})
297            }
298            Metric::String(s) => json!(s),
299            Metric::StringList(v) => json!(v),
300            Metric::Timespan(time, time_unit) => {
301                json!({"value": time_unit.duration_convert(*time), "time_unit": time_unit})
302            }
303            Metric::TimingDistribution(hist) => json!(timing_distribution::snapshot(hist)),
304            Metric::Url(s) => json!(s),
305            Metric::Uuid(s) => json!(s),
306            Metric::MemoryDistribution(hist) => json!(memory_distribution::snapshot(hist)),
307            Metric::Jwe(s) => json!(s),
308            Metric::Text(s) => json!(s),
309            Metric::Object(s) => {
310                serde_json::from_str(s).expect("object storage should have been json")
311            }
312        }
313    }
314}