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