summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/api
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/glean/api')
-rw-r--r--toolkit/components/glean/api/Cargo.toml27
-rw-r--r--toolkit/components/glean/api/src/common_test.rs58
-rw-r--r--toolkit/components/glean/api/src/factory.rs16
-rw-r--r--toolkit/components/glean/api/src/ffi/boolean.rs28
-rw-r--r--toolkit/components/glean/api/src/ffi/counter.rs28
-rw-r--r--toolkit/components/glean/api/src/ffi/custom_distribution.rs82
-rw-r--r--toolkit/components/glean/api/src/ffi/datetime.rs66
-rw-r--r--toolkit/components/glean/api/src/ffi/denominator.rs28
-rw-r--r--toolkit/components/glean/api/src/ffi/event.rs168
-rw-r--r--toolkit/components/glean/api/src/ffi/labeled.rs84
-rw-r--r--toolkit/components/glean/api/src/ffi/macros.rs289
-rw-r--r--toolkit/components/glean/api/src/ffi/memory_distribution.rs64
-rw-r--r--toolkit/components/glean/api/src/ffi/mod.rs28
-rw-r--r--toolkit/components/glean/api/src/ffi/numerator.rs35
-rw-r--r--toolkit/components/glean/api/src/ffi/ping.rs18
-rw-r--r--toolkit/components/glean/api/src/ffi/quantity.rs28
-rw-r--r--toolkit/components/glean/api/src/ffi/rate.rs40
-rw-r--r--toolkit/components/glean/api/src/ffi/string.rs33
-rw-r--r--toolkit/components/glean/api/src/ffi/string_list.rs42
-rw-r--r--toolkit/components/glean/api/src/ffi/text.rs29
-rw-r--r--toolkit/components/glean/api/src/ffi/timespan.rs48
-rw-r--r--toolkit/components/glean/api/src/ffi/timing_distribution.rs90
-rw-r--r--toolkit/components/glean/api/src/ffi/url.rs29
-rw-r--r--toolkit/components/glean/api/src/ffi/uuid.rs37
-rw-r--r--toolkit/components/glean/api/src/ipc.rs364
-rw-r--r--toolkit/components/glean/api/src/lib.rs27
-rw-r--r--toolkit/components/glean/api/src/metrics.rs20
-rw-r--r--toolkit/components/glean/api/src/pings.rs13
-rw-r--r--toolkit/components/glean/api/src/private/boolean.rs155
-rw-r--r--toolkit/components/glean/api/src/private/counter.rs204
-rw-r--r--toolkit/components/glean/api/src/private/custom_distribution.rs171
-rw-r--r--toolkit/components/glean/api/src/private/datetime.rs246
-rw-r--r--toolkit/components/glean/api/src/private/denominator.rs157
-rw-r--r--toolkit/components/glean/api/src/private/event.rs241
-rw-r--r--toolkit/components/glean/api/src/private/labeled.rs369
-rw-r--r--toolkit/components/glean/api/src/private/labeled_counter.rs179
-rw-r--r--toolkit/components/glean/api/src/private/memory_distribution.rs206
-rw-r--r--toolkit/components/glean/api/src/private/mod.rs76
-rw-r--r--toolkit/components/glean/api/src/private/numerator.rs152
-rw-r--r--toolkit/components/glean/api/src/private/ping.rs121
-rw-r--r--toolkit/components/glean/api/src/private/quantity.rs155
-rw-r--r--toolkit/components/glean/api/src/private/rate.rs186
-rw-r--r--toolkit/components/glean/api/src/private/string.rs185
-rw-r--r--toolkit/components/glean/api/src/private/string_list.rs212
-rw-r--r--toolkit/components/glean/api/src/private/text.rs179
-rw-r--r--toolkit/components/glean/api/src/private/timespan.rs180
-rw-r--r--toolkit/components/glean/api/src/private/timing_distribution.rs487
-rw-r--r--toolkit/components/glean/api/src/private/url.rs129
-rw-r--r--toolkit/components/glean/api/src/private/uuid.rs171
49 files changed, 5980 insertions, 0 deletions
diff --git a/toolkit/components/glean/api/Cargo.toml b/toolkit/components/glean/api/Cargo.toml
new file mode 100644
index 0000000000..403168fb90
--- /dev/null
+++ b/toolkit/components/glean/api/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "firefox-on-glean"
+version = "0.1.0"
+authors = ["Glean SDK team <glean-team@mozilla.com>"]
+edition = "2018"
+publish = false
+license = "MPL-2.0"
+
+[dependencies]
+bincode = "1.0"
+chrono = "0.4.10"
+glean = "57.0.0"
+inherent = "1.0.0"
+log = "0.4"
+nsstring = { path = "../../../../xpcom/rust/nsstring", optional = true }
+once_cell = "1.2.0"
+serde = { version = "1.0", features = ["derive"] }
+uuid = { version = "1.0", features = ["v4"] }
+xpcom = { path = "../../../../xpcom/rust/xpcom", optional = true }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+mozbuild = "0.1"
+
+[dev-dependencies]
+tempfile = "3.1.0"
+
+[features]
+with_gecko = ["xpcom", "nsstring"]
diff --git a/toolkit/components/glean/api/src/common_test.rs b/toolkit/components/glean/api/src/common_test.rs
new file mode 100644
index 0000000000..3c8c2c71e1
--- /dev/null
+++ b/toolkit/components/glean/api/src/common_test.rs
@@ -0,0 +1,58 @@
+// 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 std::sync::{Mutex, MutexGuard};
+
+use once_cell::sync::Lazy;
+
+const GLOBAL_APPLICATION_ID: &str = "org.mozilla.firefox.test";
+
+/// UGLY HACK.
+/// We use a global lock to force synchronization of all tests, even if run multi-threaded.
+/// This allows us to run without `--test-threads 1`.`
+pub fn lock_test() -> (MutexGuard<'static, ()>, tempfile::TempDir) {
+ static GLOBAL_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
+
+ let lock = GLOBAL_LOCK.lock().unwrap();
+
+ let dir = setup_glean(None);
+ (lock, dir)
+}
+
+// Create a new instance of Glean with a temporary directory.
+// We need to keep the `TempDir` alive, so that it's not deleted before we stop using it.
+fn setup_glean(tempdir: Option<tempfile::TempDir>) -> tempfile::TempDir {
+ let dir = match tempdir {
+ Some(tempdir) => tempdir,
+ None => tempfile::tempdir().unwrap(),
+ };
+ let tmpname = dir.path().to_path_buf();
+
+ let cfg = glean::Configuration {
+ upload_enabled: true,
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ server_endpoint: None,
+ uploader: None,
+ use_core_mps: false,
+ trim_data_to_registered_pings: false,
+ log_level: None,
+ rate_limit: None,
+ enable_event_timestamps: false,
+ experimentation_id: None,
+ };
+
+ let client_info = glean::ClientInfoMetrics {
+ app_build: "test-build".into(),
+ app_display_version: "1.2.3".into(),
+ channel: None,
+ locale: None,
+ };
+
+ glean::test_reset_glean(cfg, client_info, true);
+
+ dir
+}
diff --git a/toolkit/components/glean/api/src/factory.rs b/toolkit/components/glean/api/src/factory.rs
new file mode 100644
index 0000000000..f7984f4c46
--- /dev/null
+++ b/toolkit/components/glean/api/src/factory.rs
@@ -0,0 +1,16 @@
+// 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/.
+
+//! This file contains the Generated JOG Factory for the runtime-registration
+//! of Glean metrics in Firefox on Glean.
+//! You probably should just ignore stuff in here and ask on the
+//! [#glean Matrix channel](https://chat.mozilla.org/#/room/#glean:mozilla.org)
+//! if you have questions.
+//!
+//! Most of the contents of this module are generated by
+//! `toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py`
+
+include!(mozbuild::objdir_path!(
+ "toolkit/components/glean/api/src/factory.rs"
+));
diff --git a/toolkit/components/glean/api/src/ffi/boolean.rs b/toolkit/components/glean/api/src/ffi/boolean.rs
new file mode 100644
index 0000000000..9184b23c00
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/boolean.rs
@@ -0,0 +1,28 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub extern "C" fn fog_boolean_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(BOOLEAN_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_boolean_test_get_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(BOOLEAN_MAP, id, metric, test_get!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_boolean_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(BOOLEAN_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn fog_boolean_set(id: u32, value: bool) {
+ with_metric!(BOOLEAN_MAP, id, metric, metric.set(value));
+}
diff --git a/toolkit/components/glean/api/src/ffi/counter.rs b/toolkit/components/glean/api/src/ffi/counter.rs
new file mode 100644
index 0000000000..5fba7c0dea
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/counter.rs
@@ -0,0 +1,28 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_counter_add(id: u32, amount: i32) {
+ with_metric!(COUNTER_MAP, id, metric, metric.add(amount));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_counter_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(COUNTER_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_counter_test_get_value(id: u32, ping_name: &nsACString) -> i32 {
+ with_metric!(COUNTER_MAP, id, metric, test_get!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_counter_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(COUNTER_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/custom_distribution.rs b/toolkit/components/glean/api/src/ffi/custom_distribution.rs
new file mode 100644
index 0000000000..853a6e9845
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/custom_distribution.rs
@@ -0,0 +1,82 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+use thin_vec::ThinVec;
+
+#[no_mangle]
+pub extern "C" fn fog_custom_distribution_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(
+ CUSTOM_DISTRIBUTION_MAP,
+ id,
+ metric,
+ test_has!(metric, ping_name)
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn fog_custom_distribution_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ sum: &mut u64,
+ buckets: &mut ThinVec<u64>,
+ counts: &mut ThinVec<u64>,
+) {
+ let val = with_metric!(
+ CUSTOM_DISTRIBUTION_MAP,
+ id,
+ metric,
+ test_get!(metric, ping_name)
+ );
+ // FIXME(bug 1771885): Glean should use `u64` where it can.
+ *sum = val.sum as _;
+ for (&bucket, &count) in val.values.iter() {
+ buckets.push(bucket as _);
+ counts.push(count as _);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_custom_distribution_accumulate_samples(id: u32, samples: &ThinVec<u64>) {
+ // N.B.: Avoid reallocation here by making the underlying type take a slice.
+ let samples = samples.into_iter().map(|&i| i as i64).collect();
+ with_metric!(
+ CUSTOM_DISTRIBUTION_MAP,
+ id,
+ metric,
+ metric.accumulate_samples_signed(samples)
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn fog_custom_distribution_accumulate_samples_signed(
+ id: u32,
+ samples: &ThinVec<i64>,
+) {
+ // N.B.: Avoid reallocation here by making the underlying type take a slice.
+ let samples = samples.to_vec();
+ with_metric!(
+ CUSTOM_DISTRIBUTION_MAP,
+ id,
+ metric,
+ metric.accumulate_samples_signed(samples)
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn fog_custom_distribution_test_get_error(
+ id: u32,
+
+ error_str: &mut nsACString,
+) -> bool {
+ let err = with_metric!(
+ CUSTOM_DISTRIBUTION_MAP,
+ id,
+ metric,
+ test_get_errors!(metric)
+ );
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/datetime.rs b/toolkit/components/glean/api/src/ffi/datetime.rs
new file mode 100644
index 0000000000..7529a524e6
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/datetime.rs
@@ -0,0 +1,66 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[repr(C)]
+pub struct FogDatetime {
+ year: i32,
+ month: u32,
+ day: u32,
+ hour: u32,
+ minute: u32,
+ second: u32,
+ nano: u32,
+ offset_seconds: i32,
+}
+
+#[no_mangle]
+pub extern "C" fn fog_datetime_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(DATETIME_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_datetime_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ value: &mut FogDatetime,
+) {
+ let val = with_metric!(DATETIME_MAP, id, metric, test_get!(metric, ping_name));
+ value.year = val.year;
+ value.month = val.month;
+ value.day = val.day;
+ value.hour = val.hour;
+ value.minute = val.minute;
+ value.second = val.second;
+ value.nano = val.nanosecond;
+ value.offset_seconds = val.offset_seconds;
+}
+
+#[no_mangle]
+pub extern "C" fn fog_datetime_set(id: u32, dt: &FogDatetime) {
+ with_metric!(
+ DATETIME_MAP,
+ id,
+ metric,
+ metric.set_with_details(
+ dt.year,
+ dt.month,
+ dt.day,
+ dt.hour,
+ dt.minute,
+ dt.second,
+ dt.nano,
+ dt.offset_seconds
+ )
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn fog_datetime_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(DATETIME_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/denominator.rs b/toolkit/components/glean/api/src/ffi/denominator.rs
new file mode 100644
index 0000000000..ccb047f530
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/denominator.rs
@@ -0,0 +1,28 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_denominator_add(id: u32, amount: i32) {
+ with_metric!(DENOMINATOR_MAP, id, metric, metric.add(amount));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_denominator_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(DENOMINATOR_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_denominator_test_get_value(id: u32, ping_name: &nsACString) -> i32 {
+ with_metric!(DENOMINATOR_MAP, id, metric, test_get!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_denominator_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(DENOMINATOR_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/event.rs b/toolkit/components/glean/api/src/ffi/event.rs
new file mode 100644
index 0000000000..bd167021d6
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/event.rs
@@ -0,0 +1,168 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use std::collections::HashMap;
+
+use nsstring::{nsACString, nsCString};
+use thin_vec::ThinVec;
+
+use crate::metrics::__glean_metric_maps as metric_maps;
+use crate::private::EventRecordingError;
+
+#[no_mangle]
+pub extern "C" fn fog_event_record(
+ id: u32,
+ extra_keys: &ThinVec<nsCString>,
+ extra_values: &ThinVec<nsCString>,
+) {
+ // If no extra keys are passed, we can shortcut here.
+ if extra_keys.is_empty() {
+ if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::EVENT_MAP
+ .read()
+ .expect("Read lock for dynamic metric map was poisoned");
+ match map.get(&id.into()) {
+ Some(m) => m.record_raw(Default::default()),
+ None => panic!("No (dynamic) metric for event with id {}", id),
+ }
+ return;
+ }
+
+ if metric_maps::record_event_by_id(id, Default::default()).is_err() {
+ panic!("No event for id {}", id);
+ }
+
+ return;
+ }
+
+ assert_eq!(
+ extra_keys.len(),
+ extra_values.len(),
+ "Extra keys and values differ in length. ID: {}",
+ id
+ );
+
+ // Otherwise we need to decode them and pass them along.
+ let extra = extra_keys
+ .iter()
+ .zip(extra_values.iter())
+ .map(|(k, v)| (k.to_string(), v.to_string()))
+ .collect();
+ if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::EVENT_MAP
+ .read()
+ .expect("Read lock for dynamic metric map was poisoned");
+ match map.get(&id.into()) {
+ Some(m) => m.record_raw(extra),
+ None => panic!("No (dynamic) metric for event with id {}", id),
+ }
+ return;
+ } else {
+ match metric_maps::record_event_by_id(id, extra) {
+ Ok(()) => {}
+ Err(EventRecordingError::InvalidId) => panic!("No event for id {}", id),
+ Err(_) => panic!("Unpossible!"),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_event_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ let storage = if ping_name.is_empty() {
+ None
+ } else {
+ Some(ping_name.to_utf8().into_owned())
+ };
+ if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::EVENT_MAP
+ .read()
+ .expect("Read lock for dynamic metric map was poisoned");
+ match map.get(&id.into()) {
+ Some(m) => m.test_get_value(storage.as_deref()).is_some(),
+ None => panic!("No (dynamic) metric for event with id {}", id),
+ }
+ } else {
+ metric_maps::event_test_get_value_wrapper(id, storage).is_some()
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_event_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::EVENT_MAP
+ .read()
+ .expect("Read lock for dynamic metric map was poisoned");
+ match map.get(&id.into()) {
+ Some(m) => test_get_errors!(m),
+ None => panic!("No (dynamic) metric for event with id {}", id),
+ }
+ } else {
+ metric_maps::event_test_get_error(id)
+ };
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
+
+/// FFI-compatible representation of recorded event data.
+#[repr(C)]
+pub struct FfiRecordedEvent {
+ timestamp: u64,
+ category: nsCString,
+ name: nsCString,
+
+ /// Array of extra data, keys and values are interleaved.
+ extras: ThinVec<nsCString>,
+}
+
+#[no_mangle]
+pub extern "C" fn fog_event_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ out_events: &mut ThinVec<FfiRecordedEvent>,
+) {
+ let storage = if ping_name.is_empty() {
+ None
+ } else {
+ Some(ping_name.to_utf8().into_owned())
+ };
+
+ let events = if id & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::EVENT_MAP
+ .read()
+ .expect("Read lock for dynamic metric map was poisoned");
+ let events = match map.get(&id.into()) {
+ Some(m) => m.test_get_value(storage.as_deref()),
+ None => return,
+ };
+ match events {
+ Some(events) => events,
+ None => return,
+ }
+ } else {
+ match metric_maps::event_test_get_value_wrapper(id, storage) {
+ Some(events) => events,
+ None => return,
+ }
+ };
+
+ for event in events {
+ let extra = event.extra.unwrap_or_else(HashMap::new);
+ let extra_len = extra.len();
+ let mut extras = ThinVec::with_capacity(extra_len * 2);
+ for (k, v) in extra.into_iter() {
+ extras.push(nsCString::from(k));
+ extras.push(nsCString::from(v));
+ }
+
+ let event = FfiRecordedEvent {
+ timestamp: event.timestamp,
+ category: nsCString::from(event.category),
+ name: nsCString::from(event.name),
+ extras,
+ };
+
+ out_events.push(event);
+ }
+}
diff --git a/toolkit/components/glean/api/src/ffi/labeled.rs b/toolkit/components/glean/api/src/ffi/labeled.rs
new file mode 100644
index 0000000000..2cd61230f7
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/labeled.rs
@@ -0,0 +1,84 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use crate::metrics::__glean_metric_maps as metric_maps;
+use nsstring::nsACString;
+use std::sync::atomic::Ordering;
+
+#[no_mangle]
+pub extern "C" fn fog_labeled_enum_to_str(id: u32, label: u16, value: &mut nsACString) {
+ let val = metric_maps::labeled_enum_to_str(id, label);
+ value.assign(&val);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_labeled_boolean_get(id: u32, label: &nsACString) -> u32 {
+ labeled_submetric_get!(
+ id,
+ label,
+ LABELED_BOOLEAN_MAP,
+ labeled_boolean_get,
+ BOOLEAN_MAP,
+ LabeledBooleanMetric
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn fog_labeled_boolean_enum_get(id: u32, label: u16) -> u32 {
+ labeled_submetric_enum_get!(
+ id,
+ label,
+ labeled_boolean_enum_get,
+ BOOLEAN_MAP,
+ LabeledBooleanMetric
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn fog_labeled_counter_get(id: u32, label: &nsACString) -> u32 {
+ labeled_submetric_get!(
+ id,
+ label,
+ LABELED_COUNTER_MAP,
+ labeled_counter_get,
+ COUNTER_MAP,
+ LabeledCounterMetric
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn fog_labeled_counter_enum_get(id: u32, label: u16) -> u32 {
+ labeled_submetric_enum_get!(
+ id,
+ label,
+ labeled_counter_enum_get,
+ COUNTER_MAP,
+ LabeledCounterMetric
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn fog_labeled_string_get(id: u32, label: &nsACString) -> u32 {
+ labeled_submetric_get!(
+ id,
+ label,
+ LABELED_STRING_MAP,
+ labeled_string_get,
+ STRING_MAP,
+ LabeledStringMetric
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn fog_labeled_string_enum_get(id: u32, label: u16) -> u32 {
+ labeled_submetric_enum_get!(
+ id,
+ label,
+ labeled_string_enum_get,
+ STRING_MAP,
+ LabeledStringMetric
+ )
+}
diff --git a/toolkit/components/glean/api/src/ffi/macros.rs b/toolkit/components/glean/api/src/ffi/macros.rs
new file mode 100644
index 0000000000..3571ebd88b
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/macros.rs
@@ -0,0 +1,289 @@
+// 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/.
+
+//! Helper macros for implementing the FFI API for metric types.
+
+/// Get a metric object by ID from the corresponding map, then
+/// execute the provided closure with it.
+///
+/// # Arguments
+///
+/// * `$map` - The name of the hash map within `metrics::__glean_metric_maps`
+/// (or `factory::__jog_metric_maps`)
+/// as generated by glean_parser.
+/// * `$id` - The ID of the metric to get.
+/// * `$m` - The identifier to use for the retrieved metric.
+/// The expression `$f` can use this identifier.
+/// * `$f` - The expression to execute with the retrieved metric `$m`.
+macro_rules! with_metric {
+ (BOOLEAN_MAP, $id:ident, $m:ident, $f:expr) => {
+ maybe_labeled_with_metric!(BOOLEAN_MAP, $id, $m, $f)
+ };
+ (COUNTER_MAP, $id:ident, $m:ident, $f:expr) => {
+ maybe_labeled_with_metric!(COUNTER_MAP, $id, $m, $f)
+ };
+ (STRING_MAP, $id:ident, $m:ident, $f:expr) => {
+ maybe_labeled_with_metric!(STRING_MAP, $id, $m, $f)
+ };
+ ($map:ident, $id:ident, $m:ident, $f:expr) => {
+ just_with_metric!($map, $id, $m, $f)
+ };
+}
+
+/// Get a dynamically-registered metric object by id from the corresponding map,
+/// then execute the provided closure with it.
+///
+/// Assumes `$id` is for a dynamic non-submetric metric.
+/// Will panic if it isn't.
+///
+/// # Arguments
+///
+/// * `$map` - The name of the hash map within `factory::__jog_metric_maps`
+/// as generated by glean_parser.
+/// * `$id` - The ID of the metric to get.
+/// * `$m` - The identifier to use for the retrieved metric.
+/// The expression `$f` can use this identifier.
+/// * `$f` - The expression to execute with the retrieved metric `$m`.
+macro_rules! just_with_jog_metric {
+ ($map:ident, $id:ident, $m:ident, $f:expr) => {{
+ let map = $crate::factory::__jog_metric_maps::$map
+ .read()
+ .expect("Read lock for dynamic metric map was poisoned");
+ match map.get(&$id.into()) {
+ Some($m) => $f,
+ None => panic!("No (dynamic) metric for id {}", $id),
+ }
+ }};
+}
+
+/// Get a metric object by id from the corresponding map, then
+/// execute the provided closure with it.
+///
+/// Ignores the possibility that the $id might be for a labeled submetric.
+///
+/// # Arguments
+///
+/// * `$map` - The name of the hash map within `metrics::__glean_metric_maps`
+/// (or `factory::__jog_metric_maps`)
+/// as generated by glean_parser.
+/// * `$id` - The ID of the metric to get.
+/// * `$m` - The identifier to use for the retrieved metric.
+/// The expression `$f` can use this identifier.
+/// * `$f` - The expression to execute with the retrieved metric `$m`.
+macro_rules! just_with_metric {
+ ($map:ident, $id:ident, $m:ident, $f:expr) => {
+ if $id & (1 << $crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ just_with_jog_metric!($map, $id, $m, $f)
+ } else {
+ match $crate::metrics::__glean_metric_maps::$map.get(&$id.into()) {
+ Some($m) => $f,
+ None => panic!("No metric for id {}", $id),
+ }
+ }
+ };
+}
+
+/// Get a metric object by id from the corresponding map, then
+/// execute the provided closure with it.
+///
+/// Requires that the provided $map be of a type that can be labeled, since it
+/// assumes the presence of a same-named map in
+/// `metrics::_glean_metrics_map::submetric_maps`.
+///
+/// # Arguments
+///
+/// * `$map` - The name of the hash map within `metrics::__glean_metric_maps`
+/// and `metrics::__glean_metric_maps::submetric_maps` as generated
+/// by glean_parser.
+/// * `$id` - The ID of the metric to get.
+/// * `$m` - The identifier to use for the retrieved metric.
+/// The expression `$f` can use this identifier.
+/// * `$f` - The expression to execute with the retrieved metric `$m`.
+macro_rules! maybe_labeled_with_metric {
+ ($map:ident, $id:ident, $m:ident, $f:expr) => {
+ if $id & (1 << $crate::metrics::__glean_metric_maps::submetric_maps::SUBMETRIC_BIT) > 0 {
+ let map = $crate::metrics::__glean_metric_maps::submetric_maps::$map
+ .read()
+ .expect("Read lock for labeled metric map was poisoned");
+ match map.get(&$id.into()) {
+ Some($m) => $f,
+ None => panic!("No submetric for id {}", $id),
+ }
+ } else {
+ just_with_metric!($map, $id, $m, $f)
+ }
+ };
+}
+
+/// Test whether a value is stored for the given metric.
+///
+/// # Arguments
+///
+/// * `$metric` - The metric to test.
+/// * `$storage` - the storage name to look into.
+macro_rules! test_has {
+ ($metric:ident, $storage:ident) => {{
+ let storage = if $storage.is_empty() {
+ None
+ } else {
+ Some($storage.to_utf8())
+ };
+ $metric.test_get_value(storage.as_deref()).is_some()
+ }};
+}
+
+/// Get the currently stored value for the given metric.
+///
+/// # Arguments
+///
+/// * `$metric` - The metric to test.
+/// * `$storage` - the storage name to look into.
+macro_rules! test_get {
+ ($metric:ident, $storage:ident) => {{
+ let storage = if $storage.is_empty() {
+ None
+ } else {
+ Some($storage.to_utf8())
+ };
+ $metric.test_get_value(storage.as_deref()).unwrap()
+ }};
+}
+
+/// Check the provided metric in the provided storage for errors.
+/// On finding one, return an error string.
+///
+/// # Arguments
+///
+/// * `$metric` - The metric to test.
+macro_rules! test_get_errors {
+ ($metric:path) => {{
+ let error_types = [
+ glean::ErrorType::InvalidValue,
+ glean::ErrorType::InvalidLabel,
+ glean::ErrorType::InvalidState,
+ glean::ErrorType::InvalidOverflow,
+ ];
+ let mut error_str = None;
+ for &error_type in error_types.iter() {
+ let num_errors = $metric.test_get_num_recorded_errors(error_type);
+ if num_errors > 0 {
+ error_str = Some(format!(
+ "Metric had {} error(s) of type {}!",
+ num_errors,
+ error_type.as_str()
+ ));
+ break;
+ }
+ }
+ error_str
+ }};
+}
+
+/// Get the submetric id for a given labeled metric and label.
+///
+/// # Arguments
+///
+/// * `$id` - The id of the labeled metric.
+/// * `$label` - The (string) label of the submetric.
+/// * `$labeled_map` - The name of the labeled metric's map for retrieval (JOG only).
+/// * `$labeled_get` - The name of the labeled metric's get fn for retrieval.
+/// * `$submetric_map`- The name of the submetrics' map for storage.
+/// * `$metric_type` - The submetric's type (needed for an internal closure).
+macro_rules! labeled_submetric_get {
+ ($id:ident, $label:ident, $labeled_map:ident, $labeled_get:ident, $submetric_map:ident, $metric_type:ty) => {{
+ let tuple = ($id, $label.to_utf8().into());
+ {
+ let map = $crate::metrics::__glean_metric_maps::submetric_maps::LABELED_METRICS_TO_IDS
+ .read()
+ .expect("read lock of submetric ids was poisoned");
+ if let Some(submetric_id) = map.get(&tuple) {
+ return *submetric_id;
+ }
+ }
+
+ // Gotta actually create a new submetric with a new id.
+ let submetric_id =
+ $crate::metrics::__glean_metric_maps::submetric_maps::NEXT_LABELED_SUBMETRIC_ID
+ .fetch_add(1, Ordering::SeqCst);
+ {
+ if $id & (1 << $crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ just_with_jog_metric!($labeled_map, $id, metric, {
+ let submetric = metric.get(&tuple.1);
+ let mut map =
+ $crate::metrics::__glean_metric_maps::submetric_maps::$submetric_map
+ .write()
+ .expect("write lock of submetric map was poisoned");
+ map.insert(submetric_id.into(), submetric);
+ });
+ } else {
+ let mut map = $crate::metrics::__glean_metric_maps::submetric_maps::$submetric_map
+ .write()
+ .expect("write lock of submetric map was poisoned");
+ map.insert(
+ submetric_id.into(),
+ $crate::metrics::__glean_metric_maps::$labeled_get($id, &tuple.1),
+ );
+ }
+ }
+
+ let mut map = $crate::metrics::__glean_metric_maps::submetric_maps::LABELED_METRICS_TO_IDS
+ .write()
+ .expect("write lock of submetric ids was poisoned");
+ map.insert(tuple, submetric_id);
+ submetric_id
+ }};
+}
+
+/// Get the submetric id for a given labeled metric and label enum.
+///
+/// # Arguments
+///
+/// * `$id` - The id of the labeled metric.
+/// * `$label` - The (enum) label of the submetric.
+/// * `$labeled_get` - The name of the labeled metric's get fn for retrieval.
+/// * `$submetric_map`- The name of the submetrics' map for storage.
+/// * `$metric_type` - The submetric's type (needed for an internal closure).
+macro_rules! labeled_submetric_enum_get {
+ ($id:ident, $label_enum:ident, $labeled_get:ident, $submetric_map:ident, $metric_type:ty) => {{
+ let tuple = ($id, $label_enum.into());
+ // First: Have we seen this enum before? If so, give out the same submetric id.
+ {
+ let map = $crate::metrics::__glean_metric_maps::submetric_maps::LABELED_ENUMS_TO_IDS
+ .read()
+ .expect("read lock of enum submetric ids was poisoned");
+ if let Some(submetric_id) = map.get(&tuple) {
+ return *submetric_id;
+ }
+ }
+
+ // Alas, this is the first time we've needed to handle this metric with this enum.
+ // Gotta actually create a new submetric with a new id.
+ let submetric_id =
+ $crate::metrics::__glean_metric_maps::submetric_maps::NEXT_LABELED_SUBMETRIC_ID
+ .fetch_add(1, Ordering::SeqCst);
+ {
+ // What if the dynamic bit is set?
+ // JOG only supports JS, and enum_get isn't (yet) supported in JS.
+ assert_eq!(
+ 0,
+ $id & (1 << $crate::factory::DYNAMIC_METRIC_BIT),
+ "No enum_get support for JOG"
+ );
+ let mut map = $crate::metrics::__glean_metric_maps::submetric_maps::$submetric_map
+ .write()
+ .expect("write lock of submetric map was poisoned");
+ map.insert(
+ submetric_id.into(),
+ $crate::metrics::__glean_metric_maps::$labeled_get($id, tuple.1),
+ );
+ }
+
+ // And now ensure we store the submetric so we need not create it on subsequent calls.
+ let mut map = $crate::metrics::__glean_metric_maps::submetric_maps::LABELED_ENUMS_TO_IDS
+ .write()
+ .expect("write lock of submetric ids was poisoned");
+ map.insert(tuple, submetric_id);
+ submetric_id
+ }};
+}
diff --git a/toolkit/components/glean/api/src/ffi/memory_distribution.rs b/toolkit/components/glean/api/src/ffi/memory_distribution.rs
new file mode 100644
index 0000000000..cf09d3f8de
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/memory_distribution.rs
@@ -0,0 +1,64 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+use thin_vec::ThinVec;
+
+#[no_mangle]
+pub extern "C" fn fog_memory_distribution_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(
+ MEMORY_DISTRIBUTION_MAP,
+ id,
+ metric,
+ test_has!(metric, ping_name)
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn fog_memory_distribution_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ sum: &mut u64,
+ buckets: &mut ThinVec<u64>,
+ counts: &mut ThinVec<u64>,
+) {
+ let val = with_metric!(
+ MEMORY_DISTRIBUTION_MAP,
+ id,
+ metric,
+ test_get!(metric, ping_name)
+ );
+ *sum = val.sum as _;
+ for (&bucket, &count) in val.values.iter() {
+ buckets.push(bucket as _);
+ counts.push(count as _);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_memory_distribution_accumulate(id: u32, sample: u64) {
+ with_metric!(
+ MEMORY_DISTRIBUTION_MAP,
+ id,
+ metric,
+ metric.accumulate(sample)
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn fog_memory_distribution_test_get_error(
+ id: u32,
+
+ error_str: &mut nsACString,
+) -> bool {
+ let err = with_metric!(
+ MEMORY_DISTRIBUTION_MAP,
+ id,
+ metric,
+ test_get_errors!(metric)
+ );
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/mod.rs b/toolkit/components/glean/api/src/ffi/mod.rs
new file mode 100644
index 0000000000..23235fc2f1
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/mod.rs
@@ -0,0 +1,28 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+#[macro_use]
+mod macros;
+
+mod boolean;
+mod counter;
+mod custom_distribution;
+mod datetime;
+mod denominator;
+mod event;
+mod labeled;
+mod memory_distribution;
+mod numerator;
+mod ping;
+mod quantity;
+mod rate;
+mod string;
+mod string_list;
+mod text;
+mod timespan;
+mod timing_distribution;
+mod url;
+mod uuid;
diff --git a/toolkit/components/glean/api/src/ffi/numerator.rs b/toolkit/components/glean/api/src/ffi/numerator.rs
new file mode 100644
index 0000000000..e679e426c4
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/numerator.rs
@@ -0,0 +1,35 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_numerator_add_to_numerator(id: u32, amount: i32) {
+ with_metric!(NUMERATOR_MAP, id, metric, metric.add_to_numerator(amount));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_numerator_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(NUMERATOR_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_numerator_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ num: &mut i32,
+ den: &mut i32,
+) {
+ let rate = with_metric!(NUMERATOR_MAP, id, metric, test_get!(metric, ping_name));
+ *num = rate.numerator;
+ *den = rate.denominator;
+}
+
+#[no_mangle]
+pub extern "C" fn fog_numerator_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(NUMERATOR_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/ping.rs b/toolkit/components/glean/api/src/ffi/ping.rs
new file mode 100644
index 0000000000..2834655a03
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/ping.rs
@@ -0,0 +1,18 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use crate::pings;
+use nsstring::nsACString;
+
+#[no_mangle]
+pub extern "C" fn fog_submit_ping_by_id(id: u32, reason: &nsACString) {
+ let reason = if reason.is_empty() {
+ None
+ } else {
+ Some(reason.to_utf8())
+ };
+ pings::submit_ping_by_id(id, reason.as_deref());
+}
diff --git a/toolkit/components/glean/api/src/ffi/quantity.rs b/toolkit/components/glean/api/src/ffi/quantity.rs
new file mode 100644
index 0000000000..7f94bcff27
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/quantity.rs
@@ -0,0 +1,28 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub extern "C" fn fog_quantity_set(id: u32, value: i64) {
+ with_metric!(QUANTITY_MAP, id, metric, metric.set(value));
+}
+
+#[no_mangle]
+pub extern "C" fn fog_quantity_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(QUANTITY_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_quantity_test_get_value(id: u32, ping_name: &nsACString) -> i64 {
+ with_metric!(QUANTITY_MAP, id, metric, test_get!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_quantity_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(QUANTITY_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/rate.rs b/toolkit/components/glean/api/src/ffi/rate.rs
new file mode 100644
index 0000000000..c14b33f3ea
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/rate.rs
@@ -0,0 +1,40 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_rate_add_to_numerator(id: u32, amount: i32) {
+ with_metric!(RATE_MAP, id, metric, metric.add_to_numerator(amount));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_rate_add_to_denominator(id: u32, amount: i32) {
+ with_metric!(RATE_MAP, id, metric, metric.add_to_denominator(amount));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_rate_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(RATE_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_rate_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ num: &mut i32,
+ den: &mut i32,
+) {
+ let rate = with_metric!(RATE_MAP, id, metric, test_get!(metric, ping_name));
+ *num = rate.numerator;
+ *den = rate.denominator;
+}
+
+#[no_mangle]
+pub extern "C" fn fog_rate_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(RATE_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/string.rs b/toolkit/components/glean/api/src/ffi/string.rs
new file mode 100644
index 0000000000..fc28e03a38
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/string.rs
@@ -0,0 +1,33 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub extern "C" fn fog_string_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(STRING_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ value: &mut nsACString,
+) {
+ let val = with_metric!(STRING_MAP, id, metric, test_get!(metric, ping_name));
+ value.assign(&val);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_set(id: u32, value: &nsACString) {
+ with_metric!(STRING_MAP, id, metric, metric.set(value.to_utf8()));
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(STRING_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/string_list.rs b/toolkit/components/glean/api/src/ffi/string_list.rs
new file mode 100644
index 0000000000..42ebd9e445
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/string_list.rs
@@ -0,0 +1,42 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::{nsACString, nsCString};
+use thin_vec::ThinVec;
+
+#[no_mangle]
+pub extern "C" fn fog_string_list_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(STRING_LIST_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_list_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ value: &mut ThinVec<nsCString>,
+) {
+ let val = with_metric!(STRING_LIST_MAP, id, metric, test_get!(metric, ping_name));
+ for v in val {
+ value.push(v.into());
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_list_add(id: u32, value: &nsACString) {
+ with_metric!(STRING_LIST_MAP, id, metric, metric.add(value.to_utf8()));
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_list_set(id: u32, value: &ThinVec<nsCString>) {
+ let value = value.iter().map(|s| s.to_utf8().into()).collect();
+ with_metric!(STRING_LIST_MAP, id, metric, metric.set(value));
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_list_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(STRING_LIST_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/text.rs b/toolkit/components/glean/api/src/ffi/text.rs
new file mode 100644
index 0000000000..da46fb849f
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/text.rs
@@ -0,0 +1,29 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub extern "C" fn fog_text_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(TEXT_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_text_test_get_value(id: u32, ping_name: &nsACString, value: &mut nsACString) {
+ let val = with_metric!(TEXT_MAP, id, metric, test_get!(metric, ping_name));
+ value.assign(&val);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_text_set(id: u32, value: &nsACString) {
+ with_metric!(TEXT_MAP, id, metric, metric.set(value.to_utf8()));
+}
+
+#[no_mangle]
+pub extern "C" fn fog_text_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(TEXT_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/timespan.rs b/toolkit/components/glean/api/src/ffi/timespan.rs
new file mode 100644
index 0000000000..5de411ffab
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/timespan.rs
@@ -0,0 +1,48 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub extern "C" fn fog_timespan_start(id: u32) {
+ with_metric!(TIMESPAN_MAP, id, metric, metric.start());
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timespan_stop(id: u32) {
+ with_metric!(TIMESPAN_MAP, id, metric, metric.stop());
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timespan_cancel(id: u32) {
+ with_metric!(TIMESPAN_MAP, id, metric, metric.cancel());
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timespan_set_raw(id: u32, duration: u32) {
+ with_metric!(
+ TIMESPAN_MAP,
+ id,
+ metric,
+ metric.set_raw_unitless(duration.into())
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timespan_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(TIMESPAN_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timespan_test_get_value(id: u32, ping_name: &nsACString) -> u64 {
+ with_metric!(TIMESPAN_MAP, id, metric, test_get!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timespan_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(TIMESPAN_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/timing_distribution.rs b/toolkit/components/glean/api/src/ffi/timing_distribution.rs
new file mode 100644
index 0000000000..4ac5d03986
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/timing_distribution.rs
@@ -0,0 +1,90 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+use std::time::Duration;
+use thin_vec::ThinVec;
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_start(id: u32) -> u64 {
+ with_metric!(TIMING_DISTRIBUTION_MAP, id, metric, metric.start().id)
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_stop_and_accumulate(id: u32, timing_id: u64) {
+ with_metric!(
+ TIMING_DISTRIBUTION_MAP,
+ id,
+ metric,
+ metric.stop_and_accumulate(timing_id.into())
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_accumulate_raw_nanos(id: u32, sample: u64) {
+ with_metric!(
+ TIMING_DISTRIBUTION_MAP,
+ id,
+ metric,
+ metric.accumulate_raw_duration(Duration::from_nanos(sample))
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_cancel(id: u32, timing_id: u64) {
+ with_metric!(
+ TIMING_DISTRIBUTION_MAP,
+ id,
+ metric,
+ metric.cancel(timing_id.into())
+ );
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(
+ TIMING_DISTRIBUTION_MAP,
+ id,
+ metric,
+ test_has!(metric, ping_name)
+ )
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_test_get_value(
+ id: u32,
+ ping_name: &nsACString,
+ sum: &mut u64,
+ buckets: &mut ThinVec<u64>,
+ counts: &mut ThinVec<u64>,
+) {
+ let val = with_metric!(
+ TIMING_DISTRIBUTION_MAP,
+ id,
+ metric,
+ test_get!(metric, ping_name)
+ );
+ *sum = val.sum as _;
+ for (&bucket, &count) in val.values.iter() {
+ buckets.push(bucket as _);
+ counts.push(count as _);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_test_get_error(
+ id: u32,
+
+ error_str: &mut nsACString,
+) -> bool {
+ let err = with_metric!(
+ TIMING_DISTRIBUTION_MAP,
+ id,
+ metric,
+ test_get_errors!(metric)
+ );
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/url.rs b/toolkit/components/glean/api/src/ffi/url.rs
new file mode 100644
index 0000000000..b94915f7cf
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/url.rs
@@ -0,0 +1,29 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+
+#[no_mangle]
+pub extern "C" fn fog_url_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(URL_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_url_test_get_value(id: u32, ping_name: &nsACString, value: &mut nsACString) {
+ let val = with_metric!(URL_MAP, id, metric, test_get!(metric, ping_name));
+ value.assign(&val);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_url_set(id: u32, value: &nsACString) {
+ with_metric!(URL_MAP, id, metric, metric.set(value.to_utf8()));
+}
+
+#[no_mangle]
+pub extern "C" fn fog_url_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(URL_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ffi/uuid.rs b/toolkit/components/glean/api/src/ffi/uuid.rs
new file mode 100644
index 0000000000..e3101863ea
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/uuid.rs
@@ -0,0 +1,37 @@
+// 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/.
+
+#![cfg(feature = "with_gecko")]
+
+use nsstring::nsACString;
+use uuid::Uuid;
+
+#[no_mangle]
+pub extern "C" fn fog_uuid_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ with_metric!(UUID_MAP, id, metric, test_has!(metric, ping_name))
+}
+
+#[no_mangle]
+pub extern "C" fn fog_uuid_test_get_value(id: u32, ping_name: &nsACString, value: &mut nsACString) {
+ let uuid = with_metric!(UUID_MAP, id, metric, test_get!(metric, ping_name)).to_string();
+ value.assign(&uuid);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_uuid_set(id: u32, value: &nsACString) {
+ if let Ok(uuid) = Uuid::parse_str(&value.to_utf8()) {
+ with_metric!(UUID_MAP, id, metric, metric.set(uuid));
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_uuid_generate_and_set(id: u32) {
+ with_metric!(UUID_MAP, id, metric, metric.generate_and_set());
+}
+
+#[no_mangle]
+pub extern "C" fn fog_uuid_test_get_error(id: u32, error_str: &mut nsACString) -> bool {
+ let err = with_metric!(UUID_MAP, id, metric, test_get_errors!(metric));
+ err.map(|err_str| error_str.assign(&err_str)).is_some()
+}
diff --git a/toolkit/components/glean/api/src/ipc.rs b/toolkit/components/glean/api/src/ipc.rs
new file mode 100644
index 0000000000..9c23e1aa3b
--- /dev/null
+++ b/toolkit/components/glean/api/src/ipc.rs
@@ -0,0 +1,364 @@
+// 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/.
+
+//! IPC Implementation, Rust part
+
+use crate::private::MetricId;
+use once_cell::sync::Lazy;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+#[cfg(not(feature = "with_gecko"))]
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Mutex;
+#[cfg(feature = "with_gecko")]
+use {std::convert::TryInto, std::sync::atomic::AtomicU32, xpcom::interfaces::nsIXULRuntime};
+
+use super::metrics::__glean_metric_maps;
+
+type EventRecord = (u64, HashMap<String, String>);
+
+/// Contains all the information necessary to update the metrics on the main
+/// process.
+#[derive(Debug, Default, Deserialize, Serialize)]
+pub struct IPCPayload {
+ pub counters: HashMap<MetricId, i32>,
+ pub custom_samples: HashMap<MetricId, Vec<i64>>,
+ pub denominators: HashMap<MetricId, i32>,
+ pub events: HashMap<MetricId, Vec<EventRecord>>,
+ pub labeled_counters: HashMap<MetricId, HashMap<String, i32>>,
+ pub memory_samples: HashMap<MetricId, Vec<u64>>,
+ pub numerators: HashMap<MetricId, i32>,
+ pub rates: HashMap<MetricId, (i32, i32)>,
+ pub string_lists: HashMap<MetricId, Vec<String>>,
+ pub timing_samples: HashMap<MetricId, Vec<u64>>,
+}
+
+/// Global singleton: pending IPC payload.
+static PAYLOAD: Lazy<Mutex<IPCPayload>> = Lazy::new(|| Mutex::new(IPCPayload::default()));
+/// Global singleton: number of times the IPC payload was accessed.
+static PAYLOAD_ACCESS_COUNT: AtomicUsize = AtomicUsize::new(0);
+
+// The maximum size of an IPC message in Firefox Desktop is 256MB.
+// (See IPC::Channel::kMaximumMessageSize)
+// In `IPCPayload` the largest size can be attained in the fewest accesses via events.
+// Each event could be its own u64 id, u64 timestamp, and HashMap of ten i32 to ten 100-byte strings.
+// That's 1056B = 8 + 8 + 10(4 + 100)
+// In 256MB we can fit 254200 or so of these, not counting overhead.
+// Let's take a conservative estimate of 100000 to
+// 0) Account for overhead
+// 1) Not be greedy
+// 2) Allow time for the dispatch to main thread which will actually perform the flush
+// "Why the -1?" Because fetch_add returns the value before the addition.
+const PAYLOAD_ACCESS_WATERMARK: usize = 100000 - 1;
+
+pub fn with_ipc_payload<F, R>(f: F) -> R
+where
+ F: FnOnce(&mut IPCPayload) -> R,
+{
+ if PAYLOAD_ACCESS_COUNT.fetch_add(1, Ordering::SeqCst) > PAYLOAD_ACCESS_WATERMARK {
+ // We reset this before the actual flush to keep all the logic together.
+ // Otherwise the count reset would need to happen down in take_buf().
+ // This may overcount (resulting in undersized payloads) which is okay.
+ PAYLOAD_ACCESS_COUNT.store(0, Ordering::SeqCst);
+ handle_payload_filling();
+ }
+ let mut payload = PAYLOAD.lock().unwrap();
+ f(&mut payload)
+}
+
+/// Do we need IPC?
+///
+/// Thread-safe.
+#[cfg(feature = "with_gecko")]
+static PROCESS_TYPE: Lazy<AtomicU32> = Lazy::new(|| {
+ extern "C" {
+ fn FOG_GetProcessType() -> i32;
+ }
+ // SAFETY NOTE: Safe because it returns a primitive by value.
+ let process_type = unsafe { FOG_GetProcessType() };
+ // It's impossible for i32 to overflow u32, but maybe someone got clever
+ // and introduced a negative process type constant. Default to parent.
+ let process_type = process_type
+ .try_into()
+ .unwrap_or(nsIXULRuntime::PROCESS_TYPE_DEFAULT);
+ // We don't have process-specific init locations outside of the main
+ // process, so we introduce this side-effect to a global static init.
+ // This is the absolute first time we decide which process type we're
+ // treating this process as, so this is the earliest we can do this.
+ register_process_shutdown(process_type);
+ AtomicU32::new(process_type)
+});
+
+#[cfg(feature = "with_gecko")]
+pub fn need_ipc() -> bool {
+ PROCESS_TYPE.load(Ordering::Relaxed) != nsIXULRuntime::PROCESS_TYPE_DEFAULT
+}
+
+/// The first time we're used in a process,
+/// we'll need to start thinking about cleanup.
+///
+/// Please only call once per process.
+/// Multiple calls may register multiple handlers.
+#[cfg(feature = "with_gecko")]
+fn register_process_shutdown(process_type: u32) {
+ match process_type {
+ nsIXULRuntime::PROCESS_TYPE_DEFAULT => {
+ // Parent process shutdown is handled by the FOG XPCOM Singleton.
+ }
+ nsIXULRuntime::PROCESS_TYPE_CONTENT => {
+ // Content child shutdown is in C++ for access to RunOnShutdown().
+ extern "C" {
+ fn FOG_RegisterContentChildShutdown();
+ }
+ unsafe {
+ FOG_RegisterContentChildShutdown();
+ };
+ }
+ nsIXULRuntime::PROCESS_TYPE_GMPLUGIN => {
+ // GMP process shutdown is handled in GMPChild::ActorDestroy.
+ }
+ nsIXULRuntime::PROCESS_TYPE_GPU => {
+ // GPU process shutdown is handled in GPUParent::ActorDestroy.
+ }
+ nsIXULRuntime::PROCESS_TYPE_RDD => {
+ // RDD process shutdown is handled in RDDParent::ActorDestroy.
+ }
+ nsIXULRuntime::PROCESS_TYPE_SOCKET => {
+ // Socket process shutdown is handled in SocketProcessChild::ActorDestroy.
+ }
+ nsIXULRuntime::PROCESS_TYPE_UTILITY => {
+ // Utility process shutdown is handled in UtilityProcessChild::ActorDestroy.
+ }
+ _ => {
+ // We don't yet support other process types.
+ log::error!("Process type {} tried to use FOG, but isn't supported! (Process type constants are in nsIXULRuntime.rs)", process_type);
+ }
+ }
+}
+
+/// An RAII that, on drop, restores the value used to determine whether FOG
+/// needs IPC. Used in tests.
+/// ```rust,ignore
+/// #[test]
+/// fn test_need_ipc_raii() {
+/// assert!(false == ipc::need_ipc());
+/// {
+/// let _raii = ipc::test_set_need_ipc(true);
+/// assert!(ipc::need_ipc());
+/// }
+/// assert!(false == ipc::need_ipc());
+/// }
+/// ```
+#[cfg(not(feature = "with_gecko"))]
+pub struct TestNeedIpcRAII {
+ prev_value: bool,
+}
+
+#[cfg(not(feature = "with_gecko"))]
+impl Drop for TestNeedIpcRAII {
+ fn drop(&mut self) {
+ TEST_NEED_IPC.store(self.prev_value, Ordering::Relaxed);
+ }
+}
+
+#[cfg(not(feature = "with_gecko"))]
+static TEST_NEED_IPC: AtomicBool = AtomicBool::new(false);
+
+/// Test-only API for telling FOG to use IPC mechanisms even if the test has
+/// only the one process. See TestNeedIpcRAII for an example.
+#[cfg(not(feature = "with_gecko"))]
+pub fn test_set_need_ipc(need_ipc: bool) -> TestNeedIpcRAII {
+ TestNeedIpcRAII {
+ prev_value: TEST_NEED_IPC.swap(need_ipc, Ordering::Relaxed),
+ }
+}
+
+#[cfg(not(feature = "with_gecko"))]
+pub fn need_ipc() -> bool {
+ TEST_NEED_IPC.load(Ordering::Relaxed)
+}
+
+pub fn take_buf() -> Option<Vec<u8>> {
+ with_ipc_payload(move |payload| {
+ let buf = bincode::serialize(&payload).ok();
+ *payload = IPCPayload {
+ ..Default::default()
+ };
+ buf
+ })
+}
+
+#[cfg(not(feature = "with_gecko"))]
+fn handle_payload_filling() {
+ // Space intentionally left blank.
+ // Without Gecko IPC to drain the buffer, there's nothing we can do.
+}
+
+#[cfg(feature = "with_gecko")]
+fn handle_payload_filling() {
+ extern "C" {
+ fn FOG_IPCPayloadFull();
+ }
+ // SAFETY NOTE: Safe because it doesn't take or return values.
+ unsafe { FOG_IPCPayloadFull() };
+}
+
+#[cfg(not(feature = "with_gecko"))]
+pub fn is_in_automation() -> bool {
+ // Without Gecko IPC to drain the buffer, there's nothing we can do.
+ false
+}
+
+#[cfg(feature = "with_gecko")]
+pub fn is_in_automation() -> bool {
+ extern "C" {
+ fn FOG_IPCIsInAutomation() -> bool;
+ }
+ // SAFETY NOTE: Safe because it returns a primitive by value.
+ unsafe { FOG_IPCIsInAutomation() }
+}
+
+// Reason: We instrument the error counts,
+// but don't need more detailed error information at the moment.
+#[allow(clippy::result_unit_err)]
+pub fn replay_from_buf(buf: &[u8]) -> Result<(), ()> {
+ // TODO: Instrument failures to find metrics by id.
+ let ipc_payload: IPCPayload = bincode::deserialize(buf).map_err(|_| ())?;
+ for (id, value) in ipc_payload.counters.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::COUNTER_MAP
+ .read()
+ .expect("Read lock for dynamic counter map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ metric.add(value);
+ }
+ } else if let Some(metric) = __glean_metric_maps::COUNTER_MAP.get(&id) {
+ metric.add(value);
+ }
+ }
+ for (id, samples) in ipc_payload.custom_samples.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::CUSTOM_DISTRIBUTION_MAP
+ .read()
+ .expect("Read lock for dynamic custom distribution map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ metric.accumulate_samples_signed(samples);
+ }
+ } else if let Some(metric) = __glean_metric_maps::CUSTOM_DISTRIBUTION_MAP.get(&id) {
+ metric.accumulate_samples_signed(samples);
+ }
+ }
+ for (id, value) in ipc_payload.denominators.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::DENOMINATOR_MAP
+ .read()
+ .expect("Read lock for dynamic denominator map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ metric.add(value);
+ }
+ } else if let Some(metric) = __glean_metric_maps::DENOMINATOR_MAP.get(&id) {
+ metric.add(value);
+ }
+ }
+ for (id, records) in ipc_payload.events.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::EVENT_MAP
+ .read()
+ .expect("Read lock for dynamic event map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ for (timestamp, extra) in records.into_iter() {
+ metric.record_with_time(timestamp, extra);
+ }
+ }
+ } else {
+ for (timestamp, extra) in records.into_iter() {
+ let _ = __glean_metric_maps::record_event_by_id_with_time(id, timestamp, extra);
+ }
+ }
+ }
+ for (id, labeled_counts) in ipc_payload.labeled_counters.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::LABELED_COUNTER_MAP
+ .read()
+ .expect("Read lock for dynamic labeled counter map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ for (label, count) in labeled_counts.into_iter() {
+ metric.get(&label).add(count);
+ }
+ }
+ } else {
+ for (label, count) in labeled_counts.into_iter() {
+ __glean_metric_maps::labeled_counter_get(id.0, &label).add(count);
+ }
+ }
+ }
+ for (id, samples) in ipc_payload.memory_samples.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::MEMORY_DISTRIBUTION_MAP
+ .read()
+ .expect("Read lock for dynamic memory dist map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ samples
+ .into_iter()
+ .for_each(|sample| metric.accumulate(sample));
+ }
+ } else if let Some(metric) = __glean_metric_maps::MEMORY_DISTRIBUTION_MAP.get(&id) {
+ samples
+ .into_iter()
+ .for_each(|sample| metric.accumulate(sample));
+ }
+ }
+ for (id, value) in ipc_payload.numerators.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::NUMERATOR_MAP
+ .read()
+ .expect("Read lock for dynamic numerator map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ metric.add_to_numerator(value);
+ }
+ } else if let Some(metric) = __glean_metric_maps::NUMERATOR_MAP.get(&id) {
+ metric.add_to_numerator(value);
+ }
+ }
+ for (id, (n, d)) in ipc_payload.rates.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::RATE_MAP
+ .read()
+ .expect("Read lock for dynamic rate map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ metric.add_to_numerator(n);
+ metric.add_to_denominator(d);
+ }
+ } else if let Some(metric) = __glean_metric_maps::RATE_MAP.get(&id) {
+ metric.add_to_numerator(n);
+ metric.add_to_denominator(d);
+ }
+ }
+ for (id, strings) in ipc_payload.string_lists.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::STRING_LIST_MAP
+ .read()
+ .expect("Read lock for dynamic string list map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ strings.iter().for_each(|s| metric.add(s));
+ }
+ } else if let Some(metric) = __glean_metric_maps::STRING_LIST_MAP.get(&id) {
+ strings.iter().for_each(|s| metric.add(s));
+ }
+ }
+ for (id, samples) in ipc_payload.timing_samples.into_iter() {
+ if id.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::TIMING_DISTRIBUTION_MAP
+ .read()
+ .expect("Read lock for dynamic timing distribution map was poisoned");
+ if let Some(metric) = map.get(&id) {
+ metric.accumulate_raw_samples_nanos(samples);
+ }
+ } else if let Some(metric) = __glean_metric_maps::TIMING_DISTRIBUTION_MAP.get(&id) {
+ metric.accumulate_raw_samples_nanos(samples);
+ }
+ }
+ Ok(())
+}
diff --git a/toolkit/components/glean/api/src/lib.rs b/toolkit/components/glean/api/src/lib.rs
new file mode 100644
index 0000000000..2f51b94917
--- /dev/null
+++ b/toolkit/components/glean/api/src/lib.rs
@@ -0,0 +1,27 @@
+// 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 public FOG APIs, for Rust consumers.
+
+// Re-exporting for later use in generated code.
+pub extern crate chrono;
+pub extern crate once_cell;
+pub extern crate uuid;
+
+// Re-exporting for use in user tests.
+pub use private::{DistributionData, ErrorType, RecordedEvent};
+
+// Must appear before `metrics` or its use of `ffi`'s macros will fail.
+#[macro_use]
+mod ffi;
+
+pub mod factory;
+pub mod metrics;
+pub mod pings;
+pub mod private;
+
+pub mod ipc;
+
+#[cfg(test)]
+mod common_test;
diff --git a/toolkit/components/glean/api/src/metrics.rs b/toolkit/components/glean/api/src/metrics.rs
new file mode 100644
index 0000000000..bca886e110
--- /dev/null
+++ b/toolkit/components/glean/api/src/metrics.rs
@@ -0,0 +1,20 @@
+// 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/.
+
+//! This file contains the Generated Glean Metrics API
+//!
+//! The contents of this module are generated by
+//! `toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py`, from
+//! metrics files identified in 'toolkit/components/glean/metrics_index.py`.
+
+include!(mozbuild::objdir_path!(
+ "toolkit/components/glean/api/src/metrics.rs"
+));
+
+use crate::private::{EventMetric, ExtraKeys};
+
+/// Helper to get the number of allowed extra keys for a given event metric.
+fn extra_keys_len<K: ExtraKeys>(_event: &EventMetric<K>) -> usize {
+ K::ALLOWED_KEYS.len()
+}
diff --git a/toolkit/components/glean/api/src/pings.rs b/toolkit/components/glean/api/src/pings.rs
new file mode 100644
index 0000000000..f1d0332695
--- /dev/null
+++ b/toolkit/components/glean/api/src/pings.rs
@@ -0,0 +1,13 @@
+// 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/.
+
+//! This file contains the Generated Glean Metrics API (Ping portion)
+//!
+//! The contents of this module are generated by
+//! `toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py`, from
+//! 'toolkit/components/glean/pings.yaml`.
+
+include!(mozbuild::objdir_path!(
+ "toolkit/components/glean/api/src/pings.rs"
+));
diff --git a/toolkit/components/glean/api/src/private/boolean.rs b/toolkit/components/glean/api/src/private/boolean.rs
new file mode 100644
index 0000000000..ea9ba6b6fe
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/boolean.rs
@@ -0,0 +1,155 @@
+// 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 std::sync::Arc;
+
+use glean::traits::Boolean;
+
+use super::CommonMetricData;
+
+use crate::ipc::need_ipc;
+use crate::private::MetricId;
+
+/// A boolean metric.
+///
+/// Records a simple true or false value.
+#[derive(Clone)]
+pub enum BooleanMetric {
+ Parent(Arc<glean::private::BooleanMetric>),
+ Child(BooleanMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct BooleanMetricIpc;
+
+impl BooleanMetric {
+ /// Create a new boolean metric.
+ pub fn new(_id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ BooleanMetric::Child(BooleanMetricIpc)
+ } else {
+ BooleanMetric::Parent(Arc::new(glean::private::BooleanMetric::new(meta)))
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ BooleanMetric::Parent(_) => BooleanMetric::Child(BooleanMetricIpc),
+ BooleanMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl Boolean for BooleanMetric {
+ /// Set to the specified boolean value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `value` - the value to set.
+ pub fn set(&self, value: bool) {
+ match self {
+ BooleanMetric::Parent(p) => {
+ p.set(value);
+ }
+ BooleanMetric::Child(_) => {
+ log::error!("Unable to set boolean metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set boolean metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// Get the currently stored value as a boolean.
+ /// This doesn't clear the stored value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `ping_name` - the storage name to look into.
+ ///
+ /// ## Return value
+ ///
+ /// Returns the stored value or `None` if nothing stored.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<bool> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ BooleanMetric::Parent(p) => p.test_get_value(ping_name),
+ BooleanMetric::Child(_) => {
+ panic!("Cannot get test value for boolean metric in non-main process!",)
+ }
+ }
+ }
+
+ /// **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: glean::ErrorType) -> i32 {
+ match self {
+ BooleanMetric::Parent(p) => p.test_get_num_recorded_errors(error),
+ BooleanMetric::Child(_) => panic!(
+ "Cannot get the number of recorded errors for boolean metric in non-main process!"
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_boolean_value() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_bool;
+ metric.set(true);
+
+ assert!(metric.test_get_value("store1").unwrap());
+ }
+
+ #[test]
+ fn boolean_ipc() {
+ // BooleanMetric doesn't support IPC.
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_bool;
+
+ parent_metric.set(false);
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ // scope for need_ipc RAII
+ let _raii = ipc::test_set_need_ipc(true);
+
+ // Instrumentation calls do not panic.
+ child_metric.set(true);
+
+ // (They also shouldn't do anything,
+ // but that's not something we can inspect in this test)
+ }
+
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert!(
+ false == parent_metric.test_get_value("store1").unwrap(),
+ "Boolean metrics should only work in the parent process"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/counter.rs b/toolkit/components/glean/api/src/private/counter.rs
new file mode 100644
index 0000000000..67ad3204d6
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/counter.rs
@@ -0,0 +1,204 @@
+// 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 std::sync::Arc;
+
+use glean::traits::Counter;
+
+use super::{CommonMetricData, MetricId};
+use crate::ipc::{need_ipc, with_ipc_payload};
+
+/// A counter metric.
+///
+/// Used to count things.
+/// The value can only be incremented, not decremented.
+#[derive(Clone)]
+pub enum CounterMetric {
+ Parent {
+ /// The metric's ID.
+ ///
+ /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
+ id: MetricId,
+ inner: Arc<glean::private::CounterMetric>,
+ },
+ Child(CounterMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct CounterMetricIpc(MetricId);
+
+impl CounterMetric {
+ /// Create a new counter metric.
+ pub fn new(id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ CounterMetric::Child(CounterMetricIpc(id))
+ } else {
+ let inner = Arc::new(glean::private::CounterMetric::new(meta));
+ CounterMetric::Parent { id, inner }
+ }
+ }
+
+ /// Special-purpose ctor for use by codegen.
+ /// Only useful if the metric is:
+ /// * not disabled
+ /// * lifetime: ping
+ /// * and is sent in precisely one ping.
+ pub fn codegen_new(id: u32, category: &str, name: &str, ping: &str) -> Self {
+ if need_ipc() {
+ CounterMetric::Child(CounterMetricIpc(id.into()))
+ } else {
+ let inner = Arc::new(glean::private::CounterMetric::new(CommonMetricData {
+ category: category.into(),
+ name: name.into(),
+ send_in_pings: vec![ping.into()],
+ ..Default::default()
+ }));
+ CounterMetric::Parent {
+ id: id.into(),
+ inner,
+ }
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn metric_id(&self) -> MetricId {
+ match self {
+ CounterMetric::Parent { id, .. } => *id,
+ CounterMetric::Child(c) => c.0,
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ CounterMetric::Parent { id, .. } => CounterMetric::Child(CounterMetricIpc(*id)),
+ CounterMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl Counter for CounterMetric {
+ /// Increase the counter by `amount`.
+ ///
+ /// ## Arguments
+ ///
+ /// * `amount` - The amount to increase by. Should be positive.
+ ///
+ /// ## Notes
+ ///
+ /// Logs an error if the `amount` is 0 or negative.
+ pub fn add(&self, amount: i32) {
+ match self {
+ CounterMetric::Parent { inner, .. } => {
+ inner.add(amount);
+ }
+ CounterMetric::Child(c) => {
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.counters.get_mut(&c.0) {
+ *v += amount;
+ } else {
+ payload.counters.insert(c.0, amount);
+ }
+ });
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// Get the currently stored value as an integer.
+ /// This doesn't clear the stored value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `ping_name` - the storage name to look into.
+ ///
+ /// ## Return value
+ ///
+ /// Returns the stored value or `None` if nothing stored.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<i32> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ CounterMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ CounterMetric::Child(c) => {
+ panic!("Cannot get test value for {:?} in non-parent process!", c.0)
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// 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: glean::ErrorType) -> i32 {
+ match self {
+ CounterMetric::Parent { inner, .. } => inner.test_get_num_recorded_errors(error),
+ CounterMetric::Child(c) => panic!(
+ "Cannot get the number of recorded errors for {:?} in non-parent process!",
+ c.0
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_counter_value_parent() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_counter;
+ metric.add(1);
+
+ assert_eq!(1, metric.test_get_value("store1").unwrap());
+ }
+
+ #[test]
+ fn sets_counter_value_child() {
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_counter;
+ parent_metric.add(3);
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ // scope for need_ipc RAII
+ let _raii = ipc::test_set_need_ipc(true);
+ let metric_id = child_metric.metric_id();
+
+ child_metric.add(42);
+
+ ipc::with_ipc_payload(move |payload| {
+ assert!(
+ 42 == *payload.counters.get(&metric_id).unwrap(),
+ "Stored the correct value in the ipc payload"
+ );
+ });
+ }
+
+ assert!(
+ false == ipc::need_ipc(),
+ "RAII dropped, should not need ipc any more"
+ );
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert!(
+ 45 == parent_metric.test_get_value("store1").unwrap(),
+ "Values from the 'processes' should be summed"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/custom_distribution.rs b/toolkit/components/glean/api/src/private/custom_distribution.rs
new file mode 100644
index 0000000000..2114430898
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/custom_distribution.rs
@@ -0,0 +1,171 @@
+// 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, MetricId};
+use glean::{DistributionData, ErrorType, HistogramType};
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+use glean::traits::CustomDistribution;
+
+/// A custom distribution metric.
+///
+/// Custom distributions are used to record the distribution of arbitrary values.
+pub enum CustomDistributionMetric {
+ Parent {
+ /// The metric's ID.
+ ///
+ /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
+ id: MetricId,
+ inner: glean::private::CustomDistributionMetric,
+ },
+ Child(CustomDistributionMetricIpc),
+}
+#[derive(Debug)]
+pub struct CustomDistributionMetricIpc(MetricId);
+
+impl CustomDistributionMetric {
+ /// Create a new timing distribution metric.
+ pub fn new(
+ id: MetricId,
+ meta: CommonMetricData,
+ range_min: u64,
+ range_max: u64,
+ bucket_count: u64,
+ histogram_type: HistogramType,
+ ) -> Self {
+ if need_ipc() {
+ CustomDistributionMetric::Child(CustomDistributionMetricIpc(id))
+ } else {
+ debug_assert!(
+ range_min <= i64::MAX as u64,
+ "sensible limits enforced by glean_parser"
+ );
+ debug_assert!(
+ range_max <= i64::MAX as u64,
+ "sensible limits enforced by glean_parser"
+ );
+ debug_assert!(
+ bucket_count <= i64::MAX as u64,
+ "sensible limits enforced by glean_parser"
+ );
+ let inner = glean::private::CustomDistributionMetric::new(
+ meta,
+ range_min as i64,
+ range_max as i64,
+ bucket_count as i64,
+ histogram_type,
+ );
+ CustomDistributionMetric::Parent { id, inner }
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ CustomDistributionMetric::Parent { id, .. } => {
+ CustomDistributionMetric::Child(CustomDistributionMetricIpc(*id))
+ }
+ CustomDistributionMetric::Child(_) => {
+ panic!("Can't get a child metric from a child metric")
+ }
+ }
+ }
+}
+
+#[inherent]
+impl CustomDistribution for CustomDistributionMetric {
+ pub fn accumulate_samples_signed(&self, samples: Vec<i64>) {
+ match self {
+ CustomDistributionMetric::Parent { inner, .. } => inner.accumulate_samples(samples),
+ CustomDistributionMetric::Child(c) => {
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.custom_samples.get_mut(&c.0) {
+ v.extend(samples);
+ } else {
+ payload.custom_samples.insert(c.0, samples);
+ }
+ });
+ }
+ }
+ }
+
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<DistributionData> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ CustomDistributionMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ CustomDistributionMetric::Child(c) => {
+ panic!("Cannot get test value for {:?} in non-parent process!", c)
+ }
+ }
+ }
+
+ pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
+ match self {
+ CustomDistributionMetric::Parent { inner, .. } => {
+ inner.test_get_num_recorded_errors(error)
+ }
+ CustomDistributionMetric::Child(c) => panic!(
+ "Cannot get number of recorded errors for {:?} in non-parent process!",
+ c
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn smoke_test_custom_distribution() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_custom_dist;
+
+ metric.accumulate_samples_signed(vec![1, 2, 3]);
+
+ assert!(metric.test_get_value("store1").is_some());
+ }
+
+ #[test]
+ fn custom_distribution_child() {
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_custom_dist;
+ parent_metric.accumulate_samples_signed(vec![1, 268435458]);
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ // scope for need_ipc RAII
+ let _raii = ipc::test_set_need_ipc(true);
+
+ child_metric.accumulate_samples_signed(vec![4, 268435460]);
+ }
+
+ let buf = ipc::take_buf().unwrap();
+ assert!(buf.len() > 0);
+ assert!(ipc::replay_from_buf(&buf).is_ok());
+
+ let data = parent_metric
+ .test_get_value("store1")
+ .expect("should have some data");
+
+ assert_eq!(2, data.values[&1], "Low bucket has 2 values");
+ assert_eq!(
+ 2, data.values[&268435456],
+ "Next higher bucket has 2 values"
+ );
+ assert_eq!(
+ 1 + 4 + 268435458 + 268435460,
+ data.sum,
+ "Sum of all recorded values"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/datetime.rs b/toolkit/components/glean/api/src/private/datetime.rs
new file mode 100644
index 0000000000..f95b3c33fb
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/datetime.rs
@@ -0,0 +1,246 @@
+// 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, MetricId};
+
+use super::TimeUnit;
+use crate::ipc::need_ipc;
+use chrono::{FixedOffset, TimeZone};
+use glean::traits::Datetime;
+
+/// A datetime metric of a certain resolution.
+///
+/// Datetimes are used to make record when something happened according to the
+/// client's clock.
+#[derive(Clone)]
+pub enum DatetimeMetric {
+ Parent(glean::private::DatetimeMetric),
+ Child(DatetimeMetricIpc),
+}
+#[derive(Debug, Clone)]
+pub struct DatetimeMetricIpc;
+
+impl DatetimeMetric {
+ /// Create a new datetime metric.
+ pub fn new(_id: MetricId, meta: CommonMetricData, time_unit: TimeUnit) -> Self {
+ if need_ipc() {
+ DatetimeMetric::Child(DatetimeMetricIpc)
+ } else {
+ DatetimeMetric::Parent(glean::private::DatetimeMetric::new(meta, time_unit))
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ DatetimeMetric::Parent { .. } => DatetimeMetric::Child(DatetimeMetricIpc),
+ DatetimeMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+
+ /// Sets the metric to a date/time including the timezone offset.
+ ///
+ /// # Arguments
+ ///
+ /// * `year` - the year to set the metric to.
+ /// * `month` - the month to set the metric to (1-12).
+ /// * `day` - the day to set the metric to (1-based).
+ /// * `hour` - the hour to set the metric to (0-23).
+ /// * `minute` - the minute to set the metric to.
+ /// * `second` - the second to set the metric to.
+ /// * `nano` - the nanosecond fraction to the last whole second.
+ /// * `offset_seconds` - the timezone difference, in seconds, for the Eastern
+ /// Hemisphere. Negative seconds mean Western Hemisphere.
+ #[cfg_attr(not(feature = "with-gecko"), allow(dead_code))]
+ #[allow(clippy::too_many_arguments)]
+ pub(crate) fn set_with_details(
+ &self,
+ year: i32,
+ month: u32,
+ day: u32,
+ hour: u32,
+ minute: u32,
+ second: u32,
+ nano: u32,
+ offset_seconds: i32,
+ ) {
+ match self {
+ DatetimeMetric::Parent(p) => {
+ let tz = FixedOffset::east_opt(offset_seconds);
+ if tz.is_none() {
+ log::error!(
+ "Unable to set datetime metric with invalid offset seconds {}",
+ offset_seconds
+ );
+ // TODO: Record an error
+ return;
+ }
+
+ let value = FixedOffset::east(offset_seconds)
+ .ymd_opt(year, month, day)
+ .and_hms_nano_opt(hour, minute, second, nano);
+ match value.single() {
+ Some(d) => p.set(Some(d.into())),
+ _ => {
+ log::error!("Unable to construct datetime")
+ // TODO: Record an error
+ }
+ }
+ }
+ DatetimeMetric::Child(_) => {
+ log::error!("Unable to set datetime metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set datetime in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ }
+ }
+ }
+}
+
+#[inherent]
+impl Datetime for DatetimeMetric {
+ /// Sets the metric to a date/time which including the timezone offset.
+ ///
+ /// ## Arguments
+ ///
+ /// - `value` - The date and time and timezone value to set.
+ /// If None we use the current local time.
+ pub fn set(&self, value: Option<glean::Datetime>) {
+ match self {
+ DatetimeMetric::Parent(p) => {
+ p.set(value);
+ }
+ DatetimeMetric::Child(_) => {
+ log::error!(
+ "Unable to set datetime metric DatetimeMetric in non-main process. This operation will be ignored."
+ );
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set datetime metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ }
+ }
+ }
+
+ /// **Exported for test purposes.**
+ ///
+ /// Gets the currently stored value as a Datetime.
+ ///
+ /// The precision of this value is truncated to the `time_unit` precision.
+ ///
+ /// This doesn't clear the stored value.
+ ///
+ /// # Arguments
+ ///
+ /// * `ping_name` - represents the optional name of the ping to retrieve the
+ /// metric for. Defaults to the first value in `send_in_pings`.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<glean::Datetime> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ DatetimeMetric::Parent(p) => p.test_get_value(ping_name),
+ DatetimeMetric::Child(_) => {
+ panic!("Cannot get test value for DatetimeMetric in non-main process!")
+ }
+ }
+ }
+
+ /// **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: glean::ErrorType) -> i32 {
+ match self {
+ DatetimeMetric::Parent(p) => p.test_get_num_recorded_errors(error),
+ DatetimeMetric::Child(_) => panic!(
+ "Cannot get the number of recorded errors for DatetimeMetric in non-main process!"
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use chrono::{DateTime, FixedOffset, TimeZone};
+
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_datetime_value() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_date;
+
+ let a_datetime = FixedOffset::east(5 * 3600)
+ .ymd(2020, 05, 07)
+ .and_hms(11, 58, 00);
+ metric.set(Some(a_datetime.into()));
+
+ let expected: glean::Datetime = DateTime::parse_from_rfc3339("2020-05-07T11:58:00+05:00")
+ .unwrap()
+ .into();
+ assert_eq!(expected, metric.test_get_value("store1").unwrap());
+ }
+
+ #[test]
+ fn sets_datetime_value_with_details() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_date;
+
+ metric.set_with_details(2020, 05, 07, 11, 58, 0, 0, 5 * 3600);
+
+ let expected: glean::Datetime = DateTime::parse_from_rfc3339("2020-05-07T11:58:00+05:00")
+ .unwrap()
+ .into();
+ assert_eq!(expected, metric.test_get_value("store1").unwrap());
+ }
+
+ #[test]
+ fn datetime_ipc() {
+ // DatetimeMetric doesn't support IPC.
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_date;
+
+ // Instrumentation calls do not panic.
+ let a_datetime = FixedOffset::east(5 * 3600)
+ .ymd(2020, 10, 13)
+ .and_hms(16, 41, 00);
+ parent_metric.set(Some(a_datetime.into()));
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ let _raii = ipc::test_set_need_ipc(true);
+
+ let a_datetime = FixedOffset::east(0).ymd(2018, 4, 7).and_hms(12, 01, 00);
+ child_metric.set(Some(a_datetime.into()));
+
+ // (They also shouldn't do anything,
+ // but that's not something we can inspect in this test)
+ }
+
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ let expected: glean::Datetime = DateTime::parse_from_rfc3339("2020-10-13T16:41:00+05:00")
+ .unwrap()
+ .into();
+ assert_eq!(expected, parent_metric.test_get_value("store1").unwrap());
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/denominator.rs b/toolkit/components/glean/api/src/private/denominator.rs
new file mode 100644
index 0000000000..64982b5050
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/denominator.rs
@@ -0,0 +1,157 @@
+// 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;
+
+use glean::traits::Counter;
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+use crate::private::MetricId;
+
+/// Developer-facing API for recording counter metrics that are acting as
+/// external denominators for rate metrics.
+///
+/// Instances of this class type are automatically generated by the parsers
+/// at build time, allowing developers to record values that were previously
+/// registered in the metrics.yaml file.
+#[derive(Clone)]
+pub enum DenominatorMetric {
+ Parent {
+ /// The metric's ID.
+ ///
+ /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
+ id: MetricId,
+ inner: glean::private::DenominatorMetric,
+ },
+ Child(DenominatorMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct DenominatorMetricIpc(MetricId);
+
+impl DenominatorMetric {
+ /// The constructor used by automatically generated metrics.
+ pub fn new(id: MetricId, meta: CommonMetricData, numerators: Vec<CommonMetricData>) -> Self {
+ if need_ipc() {
+ DenominatorMetric::Child(DenominatorMetricIpc(id))
+ } else {
+ let inner = glean::private::DenominatorMetric::new(meta, numerators);
+ DenominatorMetric::Parent { id, inner }
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn metric_id(&self) -> MetricId {
+ match self {
+ DenominatorMetric::Parent { id, .. } => *id,
+ DenominatorMetric::Child(c) => c.0,
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ DenominatorMetric::Parent { id, .. } => {
+ DenominatorMetric::Child(DenominatorMetricIpc(*id))
+ }
+ DenominatorMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl Counter for DenominatorMetric {
+ pub fn add(&self, amount: i32) {
+ match self {
+ DenominatorMetric::Parent { inner, .. } => {
+ inner.add(amount);
+ }
+ DenominatorMetric::Child(c) => {
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.denominators.get_mut(&c.0) {
+ *v += amount;
+ } else {
+ payload.denominators.insert(c.0, amount);
+ }
+ });
+ }
+ }
+ }
+
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<i32> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ DenominatorMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ DenominatorMetric::Child(c) => {
+ panic!("Cannot get test value for {:?} in non-parent process!", c.0);
+ }
+ }
+ }
+
+ pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
+ match self {
+ DenominatorMetric::Parent { inner, .. } => inner.test_get_num_recorded_errors(error),
+ DenominatorMetric::Child(c) => {
+ panic!(
+ "Cannot get the number of recorded errors for {:?} in non-parent process!",
+ c.0
+ );
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_denominator_value_parent() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::an_external_denominator;
+ metric.add(1);
+
+ assert_eq!(1, metric.test_get_value("store1").unwrap());
+ }
+
+ #[test]
+ fn sets_denominator_value_child() {
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::an_external_denominator;
+ parent_metric.add(3);
+
+ {
+ // scope for need_ipc RAII
+ let child_metric = parent_metric.child_metric();
+ let _raii = ipc::test_set_need_ipc(true);
+ let metric_id = child_metric.metric_id();
+
+ child_metric.add(42);
+
+ ipc::with_ipc_payload(move |payload| {
+ assert_eq!(
+ 42,
+ *payload.denominators.get(&metric_id).unwrap(),
+ "Stored the correct value in the ipc payload"
+ );
+ });
+ }
+
+ assert_eq!(
+ false,
+ ipc::need_ipc(),
+ "RAII dropped, should not need ipc any more"
+ );
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert_eq!(
+ 45,
+ parent_metric.test_get_value("store1").unwrap(),
+ "Values from the 'processes' should be summed"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/event.rs b/toolkit/components/glean/api/src/private/event.rs
new file mode 100644
index 0000000000..11f8213bef
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/event.rs
@@ -0,0 +1,241 @@
+// 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 std::collections::HashMap;
+
+use inherent::inherent;
+
+use super::{CommonMetricData, MetricId, RecordedEvent};
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+
+use glean::traits::Event;
+pub use glean::traits::{EventRecordingError, ExtraKeys, NoExtraKeys};
+
+/// An event metric.
+///
+/// Events allow recording of e.g. individual occurences of user actions, say
+/// every time a view was open and from where. Each time you record an event, it
+/// records a timestamp, the event's name and a set of custom values.
+pub enum EventMetric<K> {
+ Parent {
+ /// The metric's ID.
+ ///
+ /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
+ id: MetricId,
+ inner: glean::private::EventMetric<K>,
+ },
+ Child(EventMetricIpc),
+}
+
+#[derive(Debug)]
+pub struct EventMetricIpc(MetricId);
+
+impl<K: 'static + ExtraKeys + Send + Sync> EventMetric<K> {
+ /// Create a new event metric.
+ pub fn new(id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ EventMetric::Child(EventMetricIpc(id))
+ } else {
+ let inner = glean::private::EventMetric::new(meta);
+ EventMetric::Parent { id, inner }
+ }
+ }
+
+ pub fn with_runtime_extra_keys(
+ id: MetricId,
+ meta: CommonMetricData,
+ allowed_extra_keys: Vec<String>,
+ ) -> Self {
+ if need_ipc() {
+ EventMetric::Child(EventMetricIpc(id))
+ } else {
+ let inner =
+ glean::private::EventMetric::with_runtime_extra_keys(meta, allowed_extra_keys);
+ EventMetric::Parent { id, inner }
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ EventMetric::Parent { id, .. } => EventMetric::Child(EventMetricIpc(*id)),
+ EventMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+
+ /// Record a new event with the raw `extra key ID -> String` map.
+ ///
+ /// Should only be used when taking in data over FFI, where extra keys only exists as IDs.
+ pub(crate) fn record_raw(&self, extra: HashMap<String, String>) {
+ let now = glean::get_timestamp_ms();
+ self.record_with_time(now, extra);
+ }
+
+ /// Record a new event with the given timestamp and the raw `extra key ID -> String` map.
+ ///
+ /// Should only be used when applying previously recorded events, e.g. from IPC.
+ pub(crate) fn record_with_time(&self, timestamp: u64, extra: HashMap<String, String>) {
+ match self {
+ EventMetric::Parent { inner, .. } => {
+ inner.record_with_time(timestamp, extra);
+ }
+ EventMetric::Child(c) => {
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.events.get_mut(&c.0) {
+ v.push((timestamp, extra));
+ } else {
+ let v = vec![(timestamp, extra)];
+ payload.events.insert(c.0, v);
+ }
+ });
+ }
+ }
+ }
+}
+
+#[inherent]
+impl<K: 'static + ExtraKeys + Send + Sync> Event for EventMetric<K> {
+ type Extra = K;
+
+ pub fn record<M: Into<Option<K>>>(&self, extra: M) {
+ match self {
+ EventMetric::Parent { inner, .. } => {
+ inner.record(extra);
+ }
+ EventMetric::Child(_) => {
+ let now = glean::get_timestamp_ms();
+ let extra = extra.into().map(|extra| extra.into_ffi_extra());
+ let extra = extra.unwrap_or_else(HashMap::new);
+ self.record_with_time(now, extra);
+ }
+ }
+ }
+
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<Vec<RecordedEvent>> {
+ match self {
+ EventMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ EventMetric::Child(_) => {
+ panic!("Cannot get test value for event metric in non-main process!",)
+ }
+ }
+ }
+
+ pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
+ match self {
+ EventMetric::Parent { inner, .. } => inner.test_get_num_recorded_errors(error),
+ EventMetric::Child(c) => panic!(
+ "Cannot get the number of recorded errors for {:?} in non-main process!",
+ c.0
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn smoke_test_event() {
+ let _lock = lock_test();
+
+ let metric = EventMetric::<NoExtraKeys>::new(
+ 0.into(),
+ CommonMetricData {
+ name: "event_metric".into(),
+ category: "telemetry".into(),
+ send_in_pings: vec!["store1".into()],
+ disabled: false,
+ ..Default::default()
+ },
+ );
+
+ // No extra keys
+ metric.record(None);
+
+ let recorded = metric.test_get_value("store1").unwrap();
+
+ assert!(recorded.iter().any(|e| e.name == "event_metric"));
+ }
+
+ #[test]
+ fn event_ipc() {
+ use metrics::test_only_ipc::AnEventExtra;
+
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::an_event;
+
+ // No extra keys
+ parent_metric.record(None);
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ // scope for need_ipc RAII.
+ let _raii = ipc::test_set_need_ipc(true);
+
+ child_metric.record(None);
+
+ let extra = AnEventExtra {
+ extra1: Some("a-child-value".into()),
+ ..Default::default()
+ };
+ child_metric.record(extra);
+ }
+
+ // Record in the parent after the child.
+ let extra = AnEventExtra {
+ extra1: Some("a-valid-value".into()),
+ ..Default::default()
+ };
+ parent_metric.record(extra);
+
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ let events = parent_metric.test_get_value("store1").unwrap();
+ assert_eq!(events.len(), 4);
+
+ // Events from the child process are last, they might get sorted later by Glean.
+ assert_eq!(events[0].extra, None);
+ assert!(events[1].extra.as_ref().unwrap().get("extra1").unwrap() == "a-valid-value");
+ assert_eq!(events[2].extra, None);
+ assert!(events[3].extra.as_ref().unwrap().get("extra1").unwrap() == "a-child-value");
+ }
+
+ #[test]
+ fn events_with_typed_extras() {
+ use metrics::test_only_ipc::EventWithExtraExtra;
+ let _lock = lock_test();
+
+ let event = &metrics::test_only_ipc::event_with_extra;
+ // Record in the parent after the child.
+ let extra = EventWithExtraExtra {
+ extra1: Some("a-valid-value".into()),
+ extra2: Some(37),
+ extra3_longer_name: Some(false),
+ };
+ event.record(extra);
+
+ let recorded = event.test_get_value("store1").unwrap();
+
+ assert_eq!(recorded.len(), 1);
+ assert!(recorded[0].extra.as_ref().unwrap().get("extra1").unwrap() == "a-valid-value");
+ assert!(recorded[0].extra.as_ref().unwrap().get("extra2").unwrap() == "37");
+ assert!(
+ recorded[0]
+ .extra
+ .as_ref()
+ .unwrap()
+ .get("extra3_longer_name")
+ .unwrap()
+ == "false"
+ );
+ }
+}
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..60034ae5f1
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/labeled.rs
@@ -0,0 +1,369 @@
+// 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;
+use std::borrow::Cow;
+use std::marker::PhantomData;
+
+/// 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, DynamicLabel>> = 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, E> {
+ /// 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>,
+
+ label_enum: PhantomData<E>,
+}
+
+impl<T, E> LabeledMetric<T, E>
+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<Cow<'static, str>>>,
+ ) -> LabeledMetric<T, E> {
+ let core = glean::private::LabeledMetric::new(meta, labels);
+ LabeledMetric {
+ id,
+ core,
+ label_enum: PhantomData,
+ }
+ }
+}
+
+#[inherent]
+impl<U, E> glean::traits::Labeled<U> for LabeledMetric<U, E>
+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::*;
+ use crate::metrics::DynamicLabel;
+
+ // Smoke test for what should be the generated code.
+ static GLOBAL_METRIC: Lazy<LabeledMetric<LabeledBooleanMetric, DynamicLabel>> =
+ 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, DynamicLabel> = 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, DynamicLabel> = 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, DynamicLabel> = 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, DynamicLabel> = LabeledMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "bool".into(),
+ category: "labeled".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ ..Default::default()
+ },
+ None,
+ );
+
+ metric.get(&"1".repeat(72)).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()];
+
+ #[allow(dead_code)]
+ enum MetricLabels {
+ Label1 = 0,
+ Label2 = 1,
+ }
+ let metric: LabeledMetric<LabeledBooleanMetric, MetricLabels> = 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)
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/labeled_counter.rs b/toolkit/components/glean/api/src/private/labeled_counter.rs
new file mode 100644
index 0000000000..73a6697601
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/labeled_counter.rs
@@ -0,0 +1,179 @@
+// 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 glean::traits::Counter;
+
+use super::CommonMetricData;
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+use crate::private::{CounterMetric, MetricId};
+use std::collections::HashMap;
+
+/// A counter metric that knows it's a labeled counter's submetric.
+///
+/// It has special work to do when in a non-parent process.
+/// When on the parent process, it dispatches calls to the normal CounterMetric.
+#[derive(Clone)]
+pub enum LabeledCounterMetric {
+ Parent(CounterMetric),
+ Child { id: MetricId, label: String },
+}
+
+impl LabeledCounterMetric {
+ /// Create a new labeled counter submetric.
+ pub fn new(id: MetricId, meta: CommonMetricData, label: String) -> Self {
+ if need_ipc() {
+ LabeledCounterMetric::Child { id, label }
+ } else {
+ LabeledCounterMetric::Parent(CounterMetric::new(id, meta))
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn metric_id(&self) -> MetricId {
+ match self {
+ LabeledCounterMetric::Parent(p) => p.metric_id(),
+ LabeledCounterMetric::Child { id, .. } => *id,
+ }
+ }
+}
+
+#[inherent]
+impl Counter for LabeledCounterMetric {
+ /// Increase the counter by `amount`.
+ ///
+ /// ## Arguments
+ ///
+ /// * `amount` - The amount to increase by. Should be positive.
+ ///
+ /// ## Notes
+ ///
+ /// Logs an error if the `amount` is 0 or negative.
+ pub fn add(&self, amount: i32) {
+ match self {
+ LabeledCounterMetric::Parent(p) => p.add(amount),
+ LabeledCounterMetric::Child { id, label } => {
+ with_ipc_payload(move |payload| {
+ if let Some(map) = payload.labeled_counters.get_mut(id) {
+ if let Some(v) = map.get_mut(label) {
+ *v += amount;
+ } else {
+ map.insert(label.to_string(), amount);
+ }
+ } else {
+ let mut map = HashMap::new();
+ map.insert(label.to_string(), amount);
+ payload.labeled_counters.insert(*id, map);
+ }
+ });
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// Get the currently stored value as an integer.
+ /// This doesn't clear the stored value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `ping_name` - the storage name to look into.
+ ///
+ /// ## Return value
+ ///
+ /// Returns the stored value or `None` if nothing stored.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<i32> {
+ match self {
+ LabeledCounterMetric::Parent(p) => p.test_get_value(ping_name),
+ LabeledCounterMetric::Child { id, .. } => {
+ panic!("Cannot get test value for {:?} in non-parent process!", id)
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// 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: glean::ErrorType) -> i32 {
+ match self {
+ LabeledCounterMetric::Parent(p) => p.test_get_num_recorded_errors(error),
+ LabeledCounterMetric::Child { id, .. } => panic!(
+ "Cannot get the number of recorded errors for {:?} in non-parent process!",
+ id
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_labeled_counter_value_parent() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_labeled_counter;
+ metric.get("a_label").add(1);
+
+ assert_eq!(1, metric.get("a_label").test_get_value("store1").unwrap());
+ }
+
+ #[test]
+ fn sets_labeled_counter_value_child() {
+ let _lock = lock_test();
+
+ let label = "some_label";
+
+ let parent_metric = &metrics::test_only_ipc::a_labeled_counter;
+ parent_metric.get(label).add(3);
+
+ {
+ // scope for need_ipc RAII
+ let _raii = ipc::test_set_need_ipc(true);
+ let child_metric = parent_metric.get(label);
+
+ let metric_id = child_metric.metric_id();
+
+ child_metric.add(42);
+
+ ipc::with_ipc_payload(move |payload| {
+ assert_eq!(
+ 42,
+ *payload
+ .labeled_counters
+ .get(&metric_id)
+ .unwrap()
+ .get(label)
+ .unwrap(),
+ "Stored the correct value in the ipc payload"
+ );
+ });
+ }
+
+ assert!(
+ false == ipc::need_ipc(),
+ "RAII dropped, should not need ipc any more"
+ );
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert_eq!(
+ 45,
+ parent_metric.get(label).test_get_value("store1").unwrap(),
+ "Values from the 'processes' should be summed"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/memory_distribution.rs b/toolkit/components/glean/api/src/private/memory_distribution.rs
new file mode 100644
index 0000000000..b529819562
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/memory_distribution.rs
@@ -0,0 +1,206 @@
+// 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 std::convert::TryInto;
+
+use super::{CommonMetricData, DistributionData, MemoryUnit, MetricId};
+
+use glean::traits::MemoryDistribution;
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+
+/// A memory distribution metric.
+///
+/// Memory distributions are used to accumulate and store memory measurements for analyzing distributions of the memory data.
+#[derive(Clone)]
+pub enum MemoryDistributionMetric {
+ Parent {
+ /// The metric's ID.
+ ///
+ /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
+ id: MetricId,
+ inner: glean::private::MemoryDistributionMetric,
+ },
+ Child(MemoryDistributionMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct MemoryDistributionMetricIpc(MetricId);
+
+impl MemoryDistributionMetric {
+ /// Create a new memory distribution metric.
+ pub fn new(id: MetricId, meta: CommonMetricData, memory_unit: MemoryUnit) -> Self {
+ if need_ipc() {
+ MemoryDistributionMetric::Child(MemoryDistributionMetricIpc(id))
+ } else {
+ let inner = glean::private::MemoryDistributionMetric::new(meta, memory_unit);
+ MemoryDistributionMetric::Parent { id, inner }
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ MemoryDistributionMetric::Parent { id, .. } => {
+ MemoryDistributionMetric::Child(MemoryDistributionMetricIpc(*id))
+ }
+ MemoryDistributionMetric::Child(_) => {
+ panic!("Can't get a child metric from a child metric")
+ }
+ }
+ }
+}
+
+#[inherent]
+impl MemoryDistribution for MemoryDistributionMetric {
+ /// Accumulates the provided sample in the metric.
+ ///
+ /// ## Arguments
+ ///
+ /// * `sample` - The sample to be recorded by the metric. The sample is assumed to be in the
+ /// configured memory unit of the metric.
+ ///
+ /// ## Notes
+ ///
+ /// Values bigger than 1 Terabyte (2<sup>40</sup> bytes) are truncated
+ /// and an `ErrorType::InvalidValue` error is recorded.
+ pub fn accumulate(&self, sample: u64) {
+ match self {
+ MemoryDistributionMetric::Parent { inner, .. } => {
+ // values are capped at 2**40.
+ // If the value doesn't fit into `i64` it's definitely to large
+ // and cause an error.
+ // glean-core handles that.
+ let sample = sample.try_into().unwrap_or_else(|_| {
+ log::warn!(
+ "Memory size too large to fit into into 64-bytes. Saturating at i64::MAX."
+ );
+ i64::MAX
+ });
+ inner.accumulate(sample);
+ }
+ MemoryDistributionMetric::Child(c) => {
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.memory_samples.get_mut(&c.0) {
+ v.push(sample);
+ } else {
+ payload.memory_samples.insert(c.0, vec![sample]);
+ }
+ });
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// Get the currently-stored histogram as a DistributionData of the serialized value.
+ /// This doesn't clear the stored value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `ping_name` - the storage name to look into.
+ ///
+ /// ## Return value
+ ///
+ /// Returns the stored value or `None` if nothing stored.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<DistributionData> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ MemoryDistributionMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ MemoryDistributionMetric::Child(c) => {
+ panic!("Cannot get test value for {:?} in non-parent process!", c.0)
+ }
+ }
+ }
+
+ /// **Exported for test purposes.**
+ ///
+ /// Gets the number of recorded errors for the given 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 recorded.
+ pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
+ match self {
+ MemoryDistributionMetric::Parent { inner, .. } => {
+ inner.test_get_num_recorded_errors(error)
+ }
+ MemoryDistributionMetric::Child(c) => panic!(
+ "Cannot get the number of recorded errors for {:?} in non-parent process!",
+ c.0
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn smoke_test_memory_distribution() {
+ let _lock = lock_test();
+
+ let metric = MemoryDistributionMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "memory_distribution_metric".into(),
+ category: "telemetry".into(),
+ send_in_pings: vec!["store1".into()],
+ disabled: false,
+ ..Default::default()
+ },
+ MemoryUnit::Kilobyte,
+ );
+
+ metric.accumulate(42);
+
+ let metric_data = metric.test_get_value("store1").unwrap();
+ assert_eq!(1, metric_data.values[&42494]);
+ assert_eq!(0, metric_data.values[&44376]);
+ assert_eq!(43008, metric_data.sum);
+ }
+
+ #[test]
+ fn memory_distribution_child() {
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_memory_dist;
+ parent_metric.accumulate(42);
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ // scope for need_ipc RAII
+ let _raii = ipc::test_set_need_ipc(true);
+ child_metric.accumulate(13 * 9);
+ }
+
+ let metric_data = parent_metric.test_get_value("store1").unwrap();
+ assert_eq!(1, metric_data.values[&42494]);
+ assert_eq!(0, metric_data.values[&44376]);
+ assert_eq!(43008, metric_data.sum);
+
+ // Single-process IPC machine goes brrrrr...
+ let buf = ipc::take_buf().unwrap();
+ assert!(buf.len() > 0);
+ assert!(ipc::replay_from_buf(&buf).is_ok());
+
+ let data = parent_metric.test_get_value(None).expect("must have data");
+ assert_eq!(2, data.values.values().fold(0, |acc, count| acc + count));
+ assert_eq!(1, data.values[&42494]);
+ assert_eq!(1, data.values[&115097]);
+ assert_eq!(162816, data.sum);
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/mod.rs b/toolkit/components/glean/api/src/private/mod.rs
new file mode 100644
index 0000000000..b0b1e11393
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/mod.rs
@@ -0,0 +1,76 @@
+// 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 serde::{Deserialize, Serialize};
+
+// Re-export of `glean` types we can re-use.
+// That way a user only needs to depend on this crate, not on glean (and there can't be a
+// version mismatch).
+pub use glean::{
+ traits, CommonMetricData, DistributionData, ErrorType, Lifetime, MemoryUnit, RecordedEvent,
+ TimeUnit, TimerId,
+};
+
+mod boolean;
+mod counter;
+mod custom_distribution;
+mod datetime;
+mod denominator;
+mod event;
+mod labeled;
+mod labeled_counter;
+mod memory_distribution;
+mod numerator;
+mod ping;
+mod quantity;
+mod rate;
+pub(crate) mod string;
+mod string_list;
+mod text;
+mod timespan;
+mod timing_distribution;
+mod url;
+mod uuid;
+
+pub use self::boolean::BooleanMetric;
+pub use self::boolean::BooleanMetric as LabeledBooleanMetric;
+pub use self::counter::CounterMetric;
+pub use self::custom_distribution::CustomDistributionMetric;
+pub use self::datetime::DatetimeMetric;
+pub use self::denominator::DenominatorMetric;
+pub use self::event::{EventMetric, EventRecordingError, ExtraKeys, NoExtraKeys};
+pub use self::labeled::LabeledMetric;
+pub use self::labeled_counter::LabeledCounterMetric;
+pub use self::memory_distribution::MemoryDistributionMetric;
+pub use self::numerator::NumeratorMetric;
+pub use self::ping::Ping;
+pub use self::quantity::QuantityMetric;
+pub use self::rate::RateMetric;
+pub use self::string::StringMetric;
+pub use self::string::StringMetric as LabeledStringMetric;
+pub use self::string_list::StringListMetric;
+pub use self::text::TextMetric;
+pub use self::timespan::TimespanMetric;
+pub use self::timing_distribution::TimingDistributionMetric;
+pub use self::url::UrlMetric;
+pub use self::uuid::UuidMetric;
+
+/// Uniquely identifies a single metric within its metric type.
+#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize, Serialize)]
+#[repr(transparent)]
+pub struct MetricId(pub(crate) u32);
+
+impl MetricId {
+ pub fn new(id: u32) -> Self {
+ Self(id)
+ }
+}
+
+impl From<u32> for MetricId {
+ fn from(id: u32) -> Self {
+ Self(id)
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/numerator.rs b/toolkit/components/glean/api/src/private/numerator.rs
new file mode 100644
index 0000000000..0a22bf5bfc
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/numerator.rs
@@ -0,0 +1,152 @@
+// 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;
+
+use glean::traits::Numerator;
+use glean::Rate;
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+use crate::private::MetricId;
+
+/// Developer-facing API for recording rate metrics with external denominators.
+///
+/// Instances of this class type are automatically generated by the parsers
+/// at build time, allowing developers to record values that were previously
+/// registered in the metrics.yaml file.
+#[derive(Clone)]
+pub enum NumeratorMetric {
+ Parent {
+ /// The metric's ID.
+ ///
+ /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
+ id: MetricId,
+ inner: glean::private::NumeratorMetric,
+ },
+ Child(NumeratorMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct NumeratorMetricIpc(MetricId);
+
+impl NumeratorMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ NumeratorMetric::Child(NumeratorMetricIpc(id))
+ } else {
+ let inner = glean::private::NumeratorMetric::new(meta);
+ NumeratorMetric::Parent { id, inner }
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn metric_id(&self) -> MetricId {
+ match self {
+ NumeratorMetric::Parent { id, .. } => *id,
+ NumeratorMetric::Child(c) => c.0,
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ NumeratorMetric::Parent { id, .. } => NumeratorMetric::Child(NumeratorMetricIpc(*id)),
+ NumeratorMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl Numerator for NumeratorMetric {
+ pub fn add_to_numerator(&self, amount: i32) {
+ match self {
+ NumeratorMetric::Parent { inner, .. } => {
+ inner.add_to_numerator(amount);
+ }
+ NumeratorMetric::Child(c) => {
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.numerators.get_mut(&c.0) {
+ *v += amount;
+ } else {
+ payload.numerators.insert(c.0, amount);
+ }
+ });
+ }
+ }
+ }
+
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<Rate> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ NumeratorMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ NumeratorMetric::Child(c) => {
+ panic!("Cannot get test value for {:?} in non-parent process!", c.0);
+ }
+ }
+ }
+
+ pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
+ match self {
+ NumeratorMetric::Parent { inner, .. } => inner.test_get_num_recorded_errors(error),
+ NumeratorMetric::Child(c) => {
+ panic!(
+ "Cannot get the number of recorded errors for {:?} in non-parent process!",
+ c.0
+ );
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_numerator_value_parent() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::rate_with_external_denominator;
+ metric.add_to_numerator(1);
+
+ assert_eq!(1, metric.test_get_value("store1").unwrap().numerator);
+ }
+
+ #[test]
+ fn sets_numerator_value_child() {
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::rate_with_external_denominator;
+ parent_metric.add_to_numerator(3);
+
+ {
+ // scope for need_ipc RAII
+ let child_metric = parent_metric.child_metric();
+ let _raii = ipc::test_set_need_ipc(true);
+ let metric_id = child_metric.metric_id();
+
+ child_metric.add_to_numerator(42);
+
+ ipc::with_ipc_payload(move |payload| {
+ assert!(
+ 42 == *payload.numerators.get(&metric_id).unwrap(),
+ "Stored the correct value in the ipc payload"
+ );
+ });
+ }
+
+ assert!(
+ false == ipc::need_ipc(),
+ "RAII dropped, should not need ipc any more"
+ );
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert!(
+ 45 == parent_metric.test_get_value("store1").unwrap().numerator,
+ "Values from the 'processes' should be summed"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/ping.rs b/toolkit/components/glean/api/src/private/ping.rs
new file mode 100644
index 0000000000..cc9585eea1
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/ping.rs
@@ -0,0 +1,121 @@
+// 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 crate::ipc::need_ipc;
+
+/// A Glean ping.
+///
+/// See [Glean Pings](https://mozilla.github.io/glean/book/user/pings/index.html).
+#[derive(Clone)]
+pub enum Ping {
+ Parent(glean::private::PingType),
+ Child,
+}
+
+impl Ping {
+ /// Create a new ping type for the given name, whether to include the client ID and whether to
+ /// send this ping empty.
+ ///
+ /// ## Arguments
+ ///
+ /// * `name` - The name of the ping.
+ /// * `include_client_id` - Whether to include the client ID in the assembled ping when submitting.
+ /// * `send_if_empty` - Whether the ping should be sent empty or not.
+ /// * `reason_codes` - The valid reason codes for this ping.
+ pub fn new<S: Into<String>>(
+ name: S,
+ include_client_id: bool,
+ send_if_empty: bool,
+ precise_timestamps: bool,
+ reason_codes: Vec<String>,
+ ) -> Self {
+ if need_ipc() {
+ Ping::Child
+ } else {
+ Ping::Parent(glean::private::PingType::new(
+ name,
+ include_client_id,
+ send_if_empty,
+ precise_timestamps,
+ reason_codes,
+ ))
+ }
+ }
+
+ /// **Test-only API**
+ ///
+ /// Attach a callback to be called right before a new ping is submitted.
+ /// The provided function is called exactly once before submitting a ping.
+ ///
+ /// Note: The callback will be called on any call to submit.
+ /// A ping might not be sent afterwards, e.g. if the ping is otherwise empty (and
+ /// `send_if_empty` is `false`).
+ pub fn test_before_next_submit(&self, cb: impl FnOnce(Option<&str>) + Send + 'static) {
+ match self {
+ Ping::Parent(p) => p.test_before_next_submit(cb),
+ Ping::Child => {
+ panic!("Cannot use ping test API from non-parent process!");
+ }
+ };
+ }
+}
+
+#[inherent]
+impl glean::traits::Ping for Ping {
+ /// Submits the ping for eventual uploading
+ ///
+ /// # Arguments
+ ///
+ /// * `reason` - the reason the ping was triggered. Included in the
+ /// `ping_info.reason` part of the payload.
+ pub fn submit(&self, reason: Option<&str>) {
+ match self {
+ Ping::Parent(p) => {
+ p.submit(reason);
+ }
+ Ping::Child => {
+ log::error!(
+ "Unable to submit ping in non-main process. This operation will be ignored."
+ );
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to submit a ping in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ }
+ };
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use once_cell::sync::Lazy;
+
+ use super::*;
+ use crate::common_test::*;
+
+ use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+ };
+
+ // Smoke test for what should be the generated code.
+ static PROTOTYPE_PING: Lazy<Ping> =
+ Lazy::new(|| Ping::new("prototype", false, true, true, vec![]));
+
+ #[test]
+ fn smoke_test_custom_ping() {
+ let _lock = lock_test();
+
+ let called = Arc::new(AtomicBool::new(false));
+ let rcalled = Arc::clone(&called);
+ PROTOTYPE_PING.test_before_next_submit(move |reason| {
+ (*rcalled).store(true, Ordering::Relaxed);
+ assert_eq!(None, reason);
+ });
+ PROTOTYPE_PING.submit(None);
+ assert!((*called).load(Ordering::Relaxed));
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/quantity.rs b/toolkit/components/glean/api/src/private/quantity.rs
new file mode 100644
index 0000000000..ee7021890e
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/quantity.rs
@@ -0,0 +1,155 @@
+// 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 glean::traits::Quantity;
+
+use super::CommonMetricData;
+
+use crate::ipc::need_ipc;
+use crate::private::MetricId;
+
+/// A quantity metric.
+///
+/// Records a single numeric value of a specific unit.
+#[derive(Clone)]
+pub enum QuantityMetric {
+ Parent(glean::private::QuantityMetric),
+ Child(QuantityMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct QuantityMetricIpc;
+
+impl QuantityMetric {
+ /// Create a new quantity metric.
+ pub fn new(_id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ QuantityMetric::Child(QuantityMetricIpc)
+ } else {
+ QuantityMetric::Parent(glean::private::QuantityMetric::new(meta))
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ QuantityMetric::Parent(_) => QuantityMetric::Child(QuantityMetricIpc),
+ QuantityMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl Quantity for QuantityMetric {
+ /// Set the value. Must be non-negative.
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The value. Must be non-negative.
+ ///
+ /// ## Notes
+ ///
+ /// Logs an error if the `value` is negative.
+ pub fn set(&self, value: i64) {
+ match self {
+ QuantityMetric::Parent(p) => {
+ p.set(value);
+ }
+ QuantityMetric::Child(_) => {
+ log::error!("Unable to set quantity metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set quantity metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// Get the currently stored value.
+ /// This doesn't clear the stored value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `ping_name` - the storage name to look into.
+ ///
+ /// ## Return value
+ ///
+ /// Returns the stored value or `None` if nothing stored.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<i64> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ QuantityMetric::Parent(p) => p.test_get_value(ping_name),
+ QuantityMetric::Child(_) => {
+ panic!("Cannot get test value for quantity metric in non-main process!",)
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// 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: glean::ErrorType) -> i32 {
+ match self {
+ QuantityMetric::Parent(p) => p.test_get_num_recorded_errors(error),
+ QuantityMetric::Child(_) => panic!(
+ "Cannot get the number of recorded errors for quantity metric in non-main process!"
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_quantity_metric() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_quantity;
+ metric.set(14);
+
+ assert_eq!(14, metric.test_get_value("store1").unwrap());
+ }
+
+ #[test]
+ fn quantity_ipc() {
+ // QuantityMetric doesn't support IPC.
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_quantity;
+
+ parent_metric.set(15);
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ // scope for need_ipc RAII
+ let _raii = ipc::test_set_need_ipc(true);
+
+ // Instrumentation calls do not panic.
+ child_metric.set(30);
+
+ // (They also shouldn't do anything,
+ // but that's not something we can inspect in this test)
+ }
+
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert_eq!(15, parent_metric.test_get_value(None).unwrap());
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/rate.rs b/toolkit/components/glean/api/src/private/rate.rs
new file mode 100644
index 0000000000..39f04db767
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/rate.rs
@@ -0,0 +1,186 @@
+// 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;
+
+use glean::traits::Rate;
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+use crate::private::MetricId;
+
+/// Developer-facing API for recording rate metrics.
+///
+/// Instances of this class type are automatically generated by the parsers
+/// at build time, allowing developers to record values that were previously
+/// registered in the metrics.yaml file.
+#[derive(Clone)]
+pub enum RateMetric {
+ Parent {
+ /// The metric's ID.
+ ///
+ /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
+ id: MetricId,
+ inner: glean::private::RateMetric,
+ },
+ Child(RateMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct RateMetricIpc(MetricId);
+
+impl RateMetric {
+ /// The public constructor used by automatically generated metrics.
+ pub fn new(id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ RateMetric::Child(RateMetricIpc(id))
+ } else {
+ let inner = glean::private::RateMetric::new(meta);
+ RateMetric::Parent { id, inner }
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn metric_id(&self) -> MetricId {
+ match self {
+ RateMetric::Parent { id, .. } => *id,
+ RateMetric::Child(c) => c.0,
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ RateMetric::Parent { id, .. } => RateMetric::Child(RateMetricIpc(*id)),
+ RateMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl Rate for RateMetric {
+ pub fn add_to_numerator(&self, amount: i32) {
+ match self {
+ RateMetric::Parent { inner, .. } => {
+ inner.add_to_numerator(amount);
+ }
+ RateMetric::Child(c) => {
+ with_ipc_payload(move |payload| {
+ if let Some(r) = payload.rates.get_mut(&c.0) {
+ r.0 += amount;
+ } else {
+ payload.rates.insert(c.0, (amount, 0));
+ }
+ });
+ }
+ }
+ }
+
+ pub fn add_to_denominator(&self, amount: i32) {
+ match self {
+ RateMetric::Parent { inner, .. } => {
+ inner.add_to_denominator(amount);
+ }
+ RateMetric::Child(c) => {
+ with_ipc_payload(move |payload| {
+ if let Some(r) = payload.rates.get_mut(&c.0) {
+ r.1 += amount;
+ } else {
+ payload.rates.insert(c.0, (0, amount));
+ }
+ });
+ }
+ }
+ }
+
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<glean::Rate> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ RateMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ RateMetric::Child(c) => {
+ panic!("Cannot get test value for {:?} in non-parent process!", c.0);
+ }
+ }
+ }
+
+ pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
+ match self {
+ RateMetric::Parent { inner, .. } => inner.test_get_num_recorded_errors(error),
+ RateMetric::Child(c) => {
+ panic!(
+ "Cannot get the number of recorded errors for {:?} in non-parent process!",
+ c.0
+ );
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+ use glean::Rate;
+
+ #[test]
+ fn sets_rate_value_parent() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::irate;
+ metric.add_to_numerator(1);
+ metric.add_to_denominator(100);
+
+ assert_eq!(
+ Rate {
+ numerator: 1,
+ denominator: 100
+ },
+ metric.test_get_value("store1").unwrap()
+ );
+ }
+
+ #[test]
+ fn sets_rate_value_child() {
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::irate;
+ parent_metric.add_to_numerator(3);
+ parent_metric.add_to_denominator(9);
+
+ {
+ // scope for need_ipc RAII
+ let child_metric = parent_metric.child_metric();
+ let _raii = ipc::test_set_need_ipc(true);
+ let metric_id = child_metric.metric_id();
+
+ child_metric.add_to_numerator(42);
+ child_metric.add_to_denominator(24);
+
+ ipc::with_ipc_payload(move |payload| {
+ assert_eq!(
+ (42, 24),
+ *payload.rates.get(&metric_id).unwrap(),
+ "Stored the correct value in the ipc payload"
+ );
+ });
+ }
+
+ assert!(
+ false == ipc::need_ipc(),
+ "RAII dropped, should not need ipc any more"
+ );
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert_eq!(
+ Rate {
+ numerator: 45,
+ denominator: 33
+ },
+ parent_metric.test_get_value("store1").unwrap(),
+ "Values from the 'processes' should be summed"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/string.rs b/toolkit/components/glean/api/src/private/string.rs
new file mode 100644
index 0000000000..e61064c3d5
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/string.rs
@@ -0,0 +1,185 @@
+// 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 std::sync::Arc;
+
+use super::{CommonMetricData, MetricId};
+use crate::ipc::need_ipc;
+
+/// A string metric.
+///
+/// Record an Unicode string value with arbitrary content.
+/// Strings are length-limited to `MAX_LENGTH_VALUE` bytes.
+///
+/// # Example
+///
+/// The following piece of code will be generated by `glean_parser`:
+///
+/// ```rust,ignore
+/// use glean::metrics::{StringMetric, CommonMetricData, Lifetime};
+/// use once_cell::sync::Lazy;
+///
+/// mod browser {
+/// pub static search_engine: Lazy<StringMetric> = Lazy::new(|| StringMetric::new(CommonMetricData {
+/// name: "search_engine".into(),
+/// category: "browser".into(),
+/// lifetime: Lifetime::Ping,
+/// disabled: false,
+/// dynamic_label: None
+/// }));
+/// }
+/// ```
+///
+/// It can then be used with:
+///
+/// ```rust,ignore
+/// browser::search_engine.set("websearch");
+/// ```
+#[derive(Clone)]
+pub enum StringMetric {
+ Parent(Arc<glean::private::StringMetric>),
+ Child(StringMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct StringMetricIpc;
+
+impl StringMetric {
+ /// Create a new string metric.
+ pub fn new(_id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ StringMetric::Child(StringMetricIpc)
+ } else {
+ StringMetric::Parent(Arc::new(glean::private::StringMetric::new(meta)))
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ StringMetric::Parent(_) => StringMetric::Child(StringMetricIpc),
+ StringMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl glean::traits::String for StringMetric {
+ /// Sets to the specified value.
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The string to set the metric to.
+ ///
+ /// ## Notes
+ ///
+ /// Truncates the value if it is longer than `MAX_STRING_LENGTH` bytes and logs an error.
+ pub fn set<S: Into<std::string::String>>(&self, value: S) {
+ match self {
+ StringMetric::Parent(p) => {
+ p.set(value.into());
+ }
+ StringMetric::Child(_) => {
+ log::error!("Unable to set string metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set string metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ }
+ };
+ }
+
+ /// **Exported for test purposes.**
+ ///
+ /// Gets the currently stored value as a string.
+ ///
+ /// This doesn't clear the stored value.
+ ///
+ /// # Arguments
+ ///
+ /// * `ping_name` - represents the optional name of the ping to retrieve the
+ /// metric for. Defaults to the first value in `send_in_pings`.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<std::string::String> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ StringMetric::Parent(p) => p.test_get_value(ping_name),
+ StringMetric::Child(_) => {
+ panic!("Cannot get test value for string metric in non-main process!")
+ }
+ }
+ }
+
+ /// **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: glean::ErrorType) -> i32 {
+ match self {
+ StringMetric::Parent(p) => p.test_get_num_recorded_errors(error),
+ StringMetric::Child(_) => panic!(
+ "Cannot get the number of recorded errors for string metric in non-main process!"
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_string_value() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_string;
+
+ metric.set("test_string_value");
+
+ assert_eq!(
+ "test_string_value",
+ metric.test_get_value("store1").unwrap()
+ );
+ }
+
+ #[test]
+ fn string_ipc() {
+ // StringMetric doesn't support IPC.
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_string;
+
+ parent_metric.set("test_parent_value");
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ let _raii = ipc::test_set_need_ipc(true);
+
+ // Instrumentation calls do not panic.
+ child_metric.set("test_string_value");
+
+ // (They also shouldn't do anything,
+ // but that's not something we can inspect in this test)
+ }
+
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert!(
+ "test_parent_value" == parent_metric.test_get_value("store1").unwrap(),
+ "String metrics should only work in the parent process"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/string_list.rs b/toolkit/components/glean/api/src/private/string_list.rs
new file mode 100644
index 0000000000..7edd4e89d4
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/string_list.rs
@@ -0,0 +1,212 @@
+// 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, MetricId};
+
+use glean::traits::StringList;
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+
+/// A string list metric.
+///
+/// This allows appending a string value with arbitrary content to a list.
+#[derive(Clone)]
+pub enum StringListMetric {
+ Parent {
+ /// The metric's ID.
+ ///
+ /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
+ id: MetricId,
+ inner: glean::private::StringListMetric,
+ },
+ Child(StringListMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct StringListMetricIpc(MetricId);
+
+impl StringListMetric {
+ /// Create a new string list metric.
+ pub fn new(id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ StringListMetric::Child(StringListMetricIpc(id))
+ } else {
+ let inner = glean::private::StringListMetric::new(meta);
+ StringListMetric::Parent { id, inner }
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ StringListMetric::Parent { id, .. } => {
+ StringListMetric::Child(StringListMetricIpc(*id))
+ }
+ StringListMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl StringList for StringListMetric {
+ /// Add a new string to the list.
+ ///
+ /// ## Arguments
+ ///
+ /// * `value` - The string to add.
+ ///
+ /// ## Notes
+ ///
+ /// Truncates the value if it is longer than `MAX_STRING_LENGTH` bytes and logs an error.
+ /// See [String list metric limits](https://mozilla.github.io/glean/book/user/metrics/string_list.html#limits).
+ pub fn add<S: Into<String>>(&self, value: S) {
+ match self {
+ StringListMetric::Parent { inner, .. } => {
+ inner.add(value.into());
+ }
+ StringListMetric::Child(c) => {
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.string_lists.get_mut(&c.0) {
+ v.push(value.into());
+ } else {
+ let v = vec![value.into()];
+ payload.string_lists.insert(c.0, v);
+ }
+ });
+ }
+ }
+ }
+
+ /// Set to a specific list of strings.
+ ///
+ /// ## Arguments
+ ///
+ /// * `value` - The list of string to set the metric to.
+ ///
+ /// ## Notes
+ ///
+ /// If passed an empty list, records an error and returns.
+ /// Truncates the list if it is longer than `MAX_LIST_LENGTH` and logs an error.
+ /// Truncates any value in the list if it is longer than `MAX_STRING_LENGTH` and logs an error.
+ pub fn set(&self, value: Vec<String>) {
+ match self {
+ StringListMetric::Parent { inner, .. } => {
+ inner.set(value);
+ }
+ StringListMetric::Child(c) => {
+ log::error!(
+ "Unable to set string list metric {:?} in non-main process. This operation will be ignored.",
+ c.0
+ );
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.assert!(!crate::ipc::is_in_automation());
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set string list metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// Get the currently stored values.
+ /// This doesn't clear the stored value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `storage_name` - the storage name to look into.
+ ///
+ /// ## Return value
+ ///
+ /// Returns the stored value or `None` if nothing stored.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<Vec<String>> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ StringListMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ StringListMetric::Child(c) => {
+ panic!("Cannot get test value for {:?} in non-parent process!", c.0)
+ }
+ }
+ }
+
+ /// **Exported for test purposes.**
+ ///
+ /// Gets the number of recorded errors for the given 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 recorded.
+ pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
+ match self {
+ StringListMetric::Parent { inner, .. } => inner.test_get_num_recorded_errors(error),
+ StringListMetric::Child(c) => panic!(
+ "Cannot get the number of recorded errors for {:?} in non-parent process!",
+ c.0
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ #[ignore] // TODO: Enable them back when bug 1677454 lands.
+ fn sets_string_list_value() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_string_list;
+
+ metric.set(vec!["test_string_value".to_string()]);
+ metric.add("another test value");
+
+ assert_eq!(
+ vec!["test_string_value", "another test value"],
+ metric.test_get_value("store1").unwrap()
+ );
+ }
+
+ #[test]
+ #[ignore] // TODO: Enable them back when bug 1677454 lands.
+ fn string_list_ipc() {
+ // StringListMetric supports IPC only for `add`, not `set`.
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_string_list;
+
+ parent_metric.set(vec!["test_string_value".to_string()]);
+ parent_metric.add("another test value");
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ // scope for need_ipc RAII
+ let _raii = ipc::test_set_need_ipc(true);
+
+ // Recording APIs do not panic, even when they don't work.
+ child_metric.set(vec!["not gonna be set".to_string()]);
+
+ child_metric.add("child_value");
+ assert!(ipc::take_buf().unwrap().len() > 0);
+ }
+
+ // TODO: implement replay. See bug 1646165.
+ // Then perform the replay and assert we have the values from both "processes".
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+ assert_eq!(
+ vec!["test_string_value", "another test value"],
+ parent_metric.test_get_value("store1").unwrap()
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/text.rs b/toolkit/components/glean/api/src/private/text.rs
new file mode 100644
index 0000000000..970ac419eb
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/text.rs
@@ -0,0 +1,179 @@
+// 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 std::sync::Arc;
+
+use super::{CommonMetricData, MetricId};
+use crate::ipc::need_ipc;
+
+/// A text metric.
+///
+/// Record a string value with arbitrary content. Supports non-ASCII
+/// characters.
+///
+/// # Example
+///
+/// The following piece of code will be generated by `glean_parser`:
+///
+/// ```rust,ignore
+/// use glean::metrics::{TextMetric, CommonMetricData, Lifetime};
+/// use once_cell::sync::Lazy;
+///
+/// mod browser {
+/// pub static bread_recipe: Lazy<TextMetric> = Lazy::new(|| TextMetric::new(CommonMetricData {
+/// name: "bread_recipe".into(),
+/// category: "browser".into(),
+/// lifetime: Lifetime::Ping,
+/// disabled: false,
+/// dynamic_label: None
+/// }));
+/// }
+/// ```
+///
+/// It can then be used with:
+///
+/// ```rust,ignore
+/// browser::bread_recipe.set("The 'baguette de tradition française' is made from wheat flour, water, yeast, and common salt. It may contain up to 2% broad bean flour, up to 0.5% soya flour, and up to 0.3% wheat malt flour.");
+/// ```
+
+#[derive(Clone)]
+pub enum TextMetric {
+ Parent(Arc<glean::private::TextMetric>),
+ Child(TextMetricIpc),
+}
+
+#[derive(Clone, Debug)]
+pub struct TextMetricIpc;
+
+impl TextMetric {
+ /// Create a new text metric.
+ pub fn new(_id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ TextMetric::Child(TextMetricIpc)
+ } else {
+ TextMetric::Parent(Arc::new(glean::private::TextMetric::new(meta)))
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ TextMetric::Parent(_) => TextMetric::Child(TextMetricIpc),
+ TextMetric::Child(_) => panic!("Can't get a child metric from a child process"),
+ }
+ }
+}
+
+#[inherent]
+impl glean::traits::Text for TextMetric {
+ /// Sets to the specified value.
+ ///
+ /// # Arguments
+ ///
+ /// * `value` - The text to set the metric to.
+ pub fn set<S: Into<std::string::String>>(&self, value: S) {
+ match self {
+ TextMetric::Parent(p) => {
+ p.set(value.into());
+ }
+ TextMetric::Child(_) => {
+ log::error!("Unable to set text metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set text metric in non-main process, which is forbidden. This panics in automation.");
+ }
+ }
+ }
+
+ /// **Exported for test purposes.**
+ ///
+ /// Gets the currently stored value as a string.
+ ///
+ /// This doesn't clear the stored value.
+ ///
+ /// # Arguments
+ ///
+ /// * `ping_name` - represents the optional name of the ping to retrieve the
+ /// metric for. Defaults to the first value in `send_in_pings`.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<std::string::String> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ TextMetric::Parent(p) => p.test_get_value(ping_name),
+ TextMetric::Child(_) => {
+ panic!("Cannot get test value for text metric in non-main process!")
+ }
+ }
+ }
+
+ /// **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: glean::ErrorType) -> i32 {
+ match self {
+ TextMetric::Parent(p) => p.test_get_num_recorded_errors(error),
+ TextMetric::Child(_) => panic!(
+ "Cannot get the number of recorded errors for text metric in non-main process!"
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_text_value() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_text;
+
+ metric.set("test_text_value");
+
+ assert_eq!("test_text_value", metric.test_get_value("store1").unwrap());
+ }
+
+ #[test]
+ fn text_ipc() {
+ // TextMetric doesn't support IPC.
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_text;
+
+ parent_metric.set("test_parent_value");
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ let _raii = ipc::test_set_need_ipc(true);
+
+ // Instrumentation calls do not panic.
+ child_metric.set("test_child_value");
+
+ // (They also shouldn't do anything,
+ // but that's not something we can inspect in this test)
+ }
+
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert!(
+ "test_parent_value" == parent_metric.test_get_value("store1").unwrap(),
+ "Text metrics should only work in the parent process"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/timespan.rs b/toolkit/components/glean/api/src/private/timespan.rs
new file mode 100644
index 0000000000..f817d6996f
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/timespan.rs
@@ -0,0 +1,180 @@
+// 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, MetricId, TimeUnit};
+use std::convert::TryInto;
+use std::time::Duration;
+
+use glean::traits::Timespan;
+
+use crate::ipc::need_ipc;
+
+/// A timespan metric.
+///
+/// Timespans are used to make a measurement of how much time is spent in a particular task.
+pub enum TimespanMetric {
+ Parent(glean::private::TimespanMetric, TimeUnit),
+ Child,
+}
+
+impl TimespanMetric {
+ /// Create a new timespan metric.
+ pub fn new(_id: MetricId, meta: CommonMetricData, time_unit: TimeUnit) -> Self {
+ if need_ipc() {
+ TimespanMetric::Child
+ } else {
+ TimespanMetric::Parent(
+ glean::private::TimespanMetric::new(meta, time_unit),
+ time_unit,
+ )
+ }
+ }
+
+ /// Only to be called from the MLA FFI.
+ /// If you don't know what that is, don't call this.
+ pub fn set_raw_unitless(&self, duration: u64) {
+ match self {
+ TimespanMetric::Parent(p, time_unit) => {
+ p.set_raw(Duration::from_nanos(time_unit.as_nanos(duration)));
+ }
+ TimespanMetric::Child => {
+ log::error!(
+ "Unable to set_raw_unitless on timespan in non-main process. This operation will be ignored."
+ );
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set_raw_unitless on timespan metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error. bug 1704504.
+ }
+ }
+ }
+}
+
+#[inherent]
+impl Timespan for TimespanMetric {
+ pub fn start(&self) {
+ match self {
+ TimespanMetric::Parent(p, _) => p.start(),
+ TimespanMetric::Child => {
+ log::error!("Unable to start timespan metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.assert!(!crate::ipc::is_in_automation());
+ assert!(!crate::ipc::is_in_automation(), "Attempted to start timespan metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error. bug 1704504.
+ }
+ }
+ }
+
+ pub fn stop(&self) {
+ match self {
+ TimespanMetric::Parent(p, _) => p.stop(),
+ TimespanMetric::Child => {
+ log::error!("Unable to stop timespan metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.assert!(!crate::ipc::is_in_automation());
+ assert!(!crate::ipc::is_in_automation(), "Attempted to stop timespan metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error. bug 1704504.
+ }
+ }
+ }
+
+ pub fn cancel(&self) {
+ match self {
+ TimespanMetric::Parent(p, _) => p.cancel(),
+ TimespanMetric::Child => {
+ log::error!("Unable to cancel timespan metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.assert!(!crate::ipc::is_in_automation());
+ assert!(!crate::ipc::is_in_automation(), "Attempted to cancel timespan metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error. bug 1704504.
+ }
+ }
+ }
+
+ pub fn set_raw(&self, elapsed: Duration) {
+ let elapsed = elapsed.as_nanos().try_into().unwrap_or(i64::MAX);
+ match self {
+ TimespanMetric::Parent(p, _) => p.set_raw_nanos(elapsed),
+ TimespanMetric::Child => {
+ log::error!("Unable to set_raw on timespan in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.assert!(!crate::ipc::is_in_automation());
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set_raw on timespan metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error. bug 1704504.
+ }
+ }
+ }
+
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<u64> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ // Conversion is ok here:
+ // Timespans are really tricky to set to excessive values with the pleasant APIs.
+ TimespanMetric::Parent(p, _) => p.test_get_value(ping_name).map(|i| i as u64),
+ TimespanMetric::Child => {
+ panic!("Cannot get test value for in non-main process!");
+ }
+ }
+ }
+
+ pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
+ match self {
+ TimespanMetric::Parent(p, _) => p.test_get_num_recorded_errors(error),
+ TimespanMetric::Child => {
+ panic!("Cannot get the number of recorded errors for timespan metric in non-main process!");
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn smoke_test_timespan() {
+ let _lock = lock_test();
+
+ let metric = TimespanMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "timespan_metric".into(),
+ category: "telemetry".into(),
+ send_in_pings: vec!["store1".into()],
+ disabled: false,
+ ..Default::default()
+ },
+ TimeUnit::Nanosecond,
+ );
+
+ metric.start();
+ // Stopping right away might not give us data, if the underlying clock source is not precise
+ // enough.
+ // So let's cancel and make sure nothing blows up.
+ metric.cancel();
+
+ assert_eq!(None, metric.test_get_value("store1"));
+ }
+
+ #[test]
+ fn timespan_ipc() {
+ let _lock = lock_test();
+ let _raii = ipc::test_set_need_ipc(true);
+
+ let child_metric = &metrics::test_only::can_we_time_it;
+
+ // Instrumentation calls do not panic.
+ child_metric.start();
+ // Stopping right away might not give us data,
+ // if the underlying clock source is not precise enough.
+ // So let's cancel and make sure nothing blows up.
+ child_metric.cancel();
+
+ // (They also shouldn't do anything,
+ // but that's not something we can inspect in this test)
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/timing_distribution.rs b/toolkit/components/glean/api/src/private/timing_distribution.rs
new file mode 100644
index 0000000000..0ab25cc900
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/timing_distribution.rs
@@ -0,0 +1,487 @@
+// 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 std::collections::HashMap;
+use std::convert::TryInto;
+use std::sync::{
+ atomic::{AtomicUsize, Ordering},
+ RwLock,
+};
+use std::time::{Duration, Instant};
+
+use super::{CommonMetricData, MetricId, TimeUnit};
+use glean::{DistributionData, ErrorType, TimerId};
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+use glean::traits::TimingDistribution;
+
+/// A timing distribution metric.
+///
+/// Timing distributions are used to accumulate and store time measurements for analyzing distributions of the timing data.
+pub enum TimingDistributionMetric {
+ Parent {
+ /// The metric's ID.
+ ///
+ /// No longer test-only, is also used for GIFFT.
+ id: MetricId,
+ inner: glean::private::TimingDistributionMetric,
+ },
+ Child(TimingDistributionMetricIpc),
+}
+#[derive(Debug)]
+pub struct TimingDistributionMetricIpc {
+ metric_id: MetricId,
+ next_timer_id: AtomicUsize,
+ instants: RwLock<HashMap<u64, Instant>>,
+}
+
+impl TimingDistributionMetric {
+ /// Create a new timing distribution metric.
+ pub fn new(id: MetricId, meta: CommonMetricData, time_unit: TimeUnit) -> Self {
+ if need_ipc() {
+ TimingDistributionMetric::Child(TimingDistributionMetricIpc {
+ metric_id: id,
+ next_timer_id: AtomicUsize::new(0),
+ instants: RwLock::new(HashMap::new()),
+ })
+ } else {
+ let inner = glean::private::TimingDistributionMetric::new(meta, time_unit);
+ TimingDistributionMetric::Parent { id, inner }
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ TimingDistributionMetric::Parent { id, .. } => {
+ TimingDistributionMetric::Child(TimingDistributionMetricIpc {
+ metric_id: *id,
+ next_timer_id: AtomicUsize::new(0),
+ instants: RwLock::new(HashMap::new()),
+ })
+ }
+ TimingDistributionMetric::Child(_) => {
+ panic!("Can't get a child metric from a child metric")
+ }
+ }
+ }
+
+ /// Accumulates a time duration sample for the provided metric.
+ ///
+ /// Adds a count to the corresponding bucket in the timing distribution.
+ /// Saturates at u64::MAX nanoseconds.
+ ///
+ /// Prefer start() and stop_and_accumulate() where possible.
+ ///
+ /// Users of this API are responsible for ensuring the timing source used
+ /// to calculate the duration is monotonic and consistent across platforms.
+ ///
+ /// # Arguments
+ ///
+ /// * `duration` - The [`Duration`] of the accumulated sample.
+ pub fn accumulate_raw_duration(&self, duration: Duration) {
+ let sample = duration.as_nanos().try_into().unwrap_or_else(|_| {
+ // TODO: Instrument this error
+ log::warn!(
+ "Elapsed nanoseconds larger than fits into 64-bytes. Saturating at u64::MAX."
+ );
+ u64::MAX
+ });
+ // May be unused in builds without gecko.
+ let _sample_ms = duration.as_millis().try_into().unwrap_or_else(|_| {
+ // TODO: Instrument this error
+ log::warn!(
+ "Elapsed milliseconds larger than fits into 32-bytes. Saturating at u32::MAX."
+ );
+ u32::MAX
+ });
+ match self {
+ TimingDistributionMetric::Parent {
+ id: _metric_id,
+ inner,
+ } => {
+ #[cfg(feature = "with_gecko")]
+ {
+ extern "C" {
+ fn GIFFT_TimingDistributionAccumulateRawMillis(metric_id: u32, sample: u32);
+ }
+ // SAFETY: using only primitives, no return value.
+ unsafe {
+ GIFFT_TimingDistributionAccumulateRawMillis(_metric_id.0, _sample_ms);
+ }
+ }
+ inner.accumulate_raw_samples_nanos(vec![sample]);
+ }
+ TimingDistributionMetric::Child(c) => {
+ #[cfg(feature = "with_gecko")]
+ {
+ extern "C" {
+ fn GIFFT_TimingDistributionAccumulateRawMillis(metric_id: u32, sample: u32);
+ }
+ // SAFETY: using only primitives, no return value.
+ unsafe {
+ GIFFT_TimingDistributionAccumulateRawMillis(c.metric_id.0, _sample_ms);
+ }
+ }
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.timing_samples.get_mut(&c.metric_id) {
+ v.push(sample);
+ } else {
+ payload.timing_samples.insert(c.metric_id, vec![sample]);
+ }
+ });
+ }
+ }
+ }
+}
+
+#[inherent]
+impl TimingDistribution for TimingDistributionMetric {
+ /// Starts tracking time for the provided metric.
+ ///
+ /// This records an error if it’s already tracking time (i.e.
+ /// [`start`](TimingDistribution::start) was already called with no corresponding
+ /// [`stop_and_accumulate`](TimingDistribution::stop_and_accumulate)): in that case the
+ /// original start time will be preserved.
+ ///
+ /// # Returns
+ ///
+ /// A unique [`TimerId`] for the new timer.
+ pub fn start(&self) -> TimerId {
+ match self {
+ TimingDistributionMetric::Parent { id: _id, inner } => {
+ let timer_id = inner.start();
+ #[cfg(feature = "with_gecko")]
+ {
+ extern "C" {
+ fn GIFFT_TimingDistributionStart(metric_id: u32, timer_id: u64);
+ }
+ // SAFETY: using only primitives, no return value.
+ unsafe {
+ GIFFT_TimingDistributionStart(_id.0, timer_id.id);
+ }
+ }
+ timer_id.into()
+ }
+ TimingDistributionMetric::Child(c) => {
+ // There is no glean-core on this process to give us a TimerId,
+ // so we'll have to make our own and do our own bookkeeping.
+ let id = c
+ .next_timer_id
+ .fetch_add(1, Ordering::SeqCst)
+ .try_into()
+ .unwrap();
+ let mut map = c
+ .instants
+ .write()
+ .expect("lock of instants map was poisoned");
+ if let Some(_v) = map.insert(id, Instant::now()) {
+ // TODO: report an error and find a different TimerId.
+ }
+ #[cfg(feature = "with_gecko")]
+ {
+ extern "C" {
+ fn GIFFT_TimingDistributionStart(metric_id: u32, timer_id: u64);
+ }
+ // SAFETY: using only primitives, no return value.
+ unsafe {
+ GIFFT_TimingDistributionStart(c.metric_id.0, id);
+ }
+ }
+ id.into()
+ }
+ }
+ }
+
+ /// Stops tracking time for the provided metric and associated timer id.
+ ///
+ /// Adds a count to the corresponding bucket in the timing distribution.
+ /// This will record an error if no [`start`](TimingDistribution::start) was
+ /// called.
+ ///
+ /// # Arguments
+ ///
+ /// * `id` - The [`TimerId`] to associate with this timing. This allows
+ /// for concurrent timing of events associated with different ids to the
+ /// same timespan metric.
+ pub fn stop_and_accumulate(&self, id: TimerId) {
+ match self {
+ TimingDistributionMetric::Parent {
+ id: _metric_id,
+ inner,
+ } => {
+ #[cfg(feature = "with_gecko")]
+ {
+ extern "C" {
+ fn GIFFT_TimingDistributionStopAndAccumulate(metric_id: u32, timer_id: u64);
+ }
+ // SAFETY: using only primitives, no return value.
+ unsafe {
+ GIFFT_TimingDistributionStopAndAccumulate(_metric_id.0, id.id);
+ }
+ }
+ inner.stop_and_accumulate(id);
+ }
+ TimingDistributionMetric::Child(c) => {
+ #[cfg(feature = "with_gecko")]
+ {
+ extern "C" {
+ fn GIFFT_TimingDistributionStopAndAccumulate(metric_id: u32, timer_id: u64);
+ }
+ // SAFETY: using only primitives, no return value.
+ unsafe {
+ GIFFT_TimingDistributionStopAndAccumulate(c.metric_id.0, id.id);
+ }
+ }
+ let mut map = c
+ .instants
+ .write()
+ .expect("Write lock must've been poisoned.");
+ if let Some(start) = map.remove(&id.id) {
+ let now = Instant::now();
+ let sample = now
+ .checked_duration_since(start)
+ .map(|s| s.as_nanos().try_into());
+ let sample = match sample {
+ Some(Ok(sample)) => sample,
+ Some(Err(_)) => {
+ log::warn!("Elapsed time larger than fits into 64-bytes. Saturating at u64::MAX.");
+ u64::MAX
+ }
+ None => {
+ log::warn!("Time went backwards. Not recording.");
+ // TODO: report an error (timer id for stop was started, but time went backwards).
+ return;
+ }
+ };
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.timing_samples.get_mut(&c.metric_id) {
+ v.push(sample);
+ } else {
+ payload.timing_samples.insert(c.metric_id, vec![sample]);
+ }
+ });
+ } else {
+ // TODO: report an error (timer id for stop wasn't started).
+ }
+ }
+ }
+ }
+
+ /// Aborts a previous [`start`](TimingDistribution::start) call. No
+ /// error is recorded if no [`start`](TimingDistribution::start) was
+ /// called.
+ ///
+ /// # Arguments
+ ///
+ /// * `id` - The [`TimerId`] to associate with this timing. This allows
+ /// for concurrent timing of events associated with different ids to the
+ /// same timing distribution metric.
+ pub fn cancel(&self, id: TimerId) {
+ match self {
+ TimingDistributionMetric::Parent {
+ id: _metric_id,
+ inner,
+ } => {
+ #[cfg(feature = "with_gecko")]
+ {
+ extern "C" {
+ fn GIFFT_TimingDistributionCancel(metric_id: u32, timer_id: u64);
+ }
+ // SAFETY: using only primitives, no return value.
+ unsafe {
+ GIFFT_TimingDistributionCancel(_metric_id.0, id.id);
+ }
+ }
+ inner.cancel(id);
+ }
+ TimingDistributionMetric::Child(c) => {
+ let mut map = c
+ .instants
+ .write()
+ .expect("Write lock must've been poisoned.");
+ if map.remove(&id.id).is_none() {
+ // TODO: report an error (cancelled a non-started id).
+ }
+ #[cfg(feature = "with_gecko")]
+ {
+ extern "C" {
+ fn GIFFT_TimingDistributionCancel(metric_id: u32, timer_id: u64);
+ }
+ // SAFETY: using only primitives, no return value.
+ unsafe {
+ GIFFT_TimingDistributionCancel(c.metric_id.0, id.id);
+ }
+ }
+ }
+ }
+ }
+
+ /// Accumulates the provided signed samples in the metric.
+ ///
+ /// This is required so that the platform-specific code can provide us with
+ /// 64 bit signed integers if no `u64` comparable type is available. This
+ /// will take care of filtering and reporting errors for any provided negative
+ /// sample.
+ ///
+ /// Please note that this assumes that the provided samples are already in
+ /// the "unit" declared by the instance of the metric type (e.g. if the
+ /// instance this method was called on is using [`crate::TimeUnit::Second`], then
+ /// `samples` are assumed to be in that unit).
+ ///
+ /// # Arguments
+ ///
+ /// * `samples` - The vector holding the samples to be recorded by the metric.
+ ///
+ /// ## Notes
+ ///
+ /// Discards any negative value in `samples` and report an [`ErrorType::InvalidValue`]
+ /// for each of them. Reports an [`ErrorType::InvalidOverflow`] error for samples that
+ /// are longer than `MAX_SAMPLE_TIME`.
+ pub fn accumulate_samples(&self, samples: Vec<i64>) {
+ match self {
+ TimingDistributionMetric::Parent { id: _id, inner } => {
+ inner.accumulate_samples(samples)
+ }
+ TimingDistributionMetric::Child(_c) => {
+ // TODO: Instrument this error
+ log::error!("Can't record samples for a timing distribution from a child metric");
+ }
+ }
+ }
+
+ /// Accumulates the provided samples in the metric.
+ ///
+ /// # Arguments
+ ///
+ /// * `samples` - A list of samples recorded by the metric.
+ /// Samples must be in nanoseconds.
+ /// ## Notes
+ ///
+ /// Reports an [`ErrorType::InvalidOverflow`] error for samples that
+ /// are longer than `MAX_SAMPLE_TIME`.
+ pub fn accumulate_raw_samples_nanos(&self, samples: Vec<u64>) {
+ match self {
+ TimingDistributionMetric::Parent { id: _id, inner } => {
+ inner.accumulate_raw_samples_nanos(samples)
+ }
+ TimingDistributionMetric::Child(_c) => {
+ // TODO: Instrument this error
+ log::error!("Can't record samples for a timing distribution from a child metric");
+ }
+ }
+ }
+
+ /// **Exported for test purposes.**
+ ///
+ /// Gets the currently stored value of the metric.
+ ///
+ /// This doesn't clear the stored value.
+ ///
+ /// # Arguments
+ ///
+ /// * `ping_name` - represents the optional name of the ping to retrieve the
+ /// metric for. Defaults to the first value in `send_in_pings`.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<DistributionData> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ TimingDistributionMetric::Parent { inner, .. } => inner.test_get_value(ping_name),
+ TimingDistributionMetric::Child(c) => {
+ panic!("Cannot get test value for {:?} in non-parent process!", c)
+ }
+ }
+ }
+
+ /// **Exported for test purposes.**
+ ///
+ /// Gets the number of recorded errors for the given 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 recorded.
+ pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
+ match self {
+ TimingDistributionMetric::Parent { inner, .. } => {
+ inner.test_get_num_recorded_errors(error)
+ }
+ TimingDistributionMetric::Child(c) => panic!(
+ "Cannot get number of recorded errors for {:?} in non-parent process!",
+ c
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn smoke_test_timing_distribution() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_timing_dist;
+
+ let id = metric.start();
+ // Stopping right away might not give us data, if the underlying clock source is not precise
+ // enough.
+ // So let's cancel and make sure nothing blows up.
+ metric.cancel(id);
+
+ // We can't inspect the values yet.
+ assert!(metric.test_get_value("store1").is_none());
+ }
+
+ #[test]
+ fn timing_distribution_child() {
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_timing_dist;
+ let id = parent_metric.start();
+ std::thread::sleep(std::time::Duration::from_millis(10));
+ parent_metric.stop_and_accumulate(id);
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ // scope for need_ipc RAII
+ let _raii = ipc::test_set_need_ipc(true);
+
+ let id = child_metric.start();
+ let id2 = child_metric.start();
+ assert_ne!(id, id2);
+ std::thread::sleep(std::time::Duration::from_millis(10));
+ child_metric.stop_and_accumulate(id);
+
+ child_metric.cancel(id2);
+ }
+
+ let buf = ipc::take_buf().unwrap();
+ assert!(buf.len() > 0);
+ assert!(ipc::replay_from_buf(&buf).is_ok());
+
+ let data = parent_metric
+ .test_get_value("store1")
+ .expect("should have some data");
+
+ // No guarantees from timers means no guarantees on buckets.
+ // But we can guarantee it's only two samples.
+ assert_eq!(
+ 2,
+ data.values.values().fold(0, |acc, count| acc + count),
+ "record 2 values, one parent, one child measurement"
+ );
+ assert!(0 < data.sum, "record some time");
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/url.rs b/toolkit/components/glean/api/src/private/url.rs
new file mode 100644
index 0000000000..09f21dad5e
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/url.rs
@@ -0,0 +1,129 @@
+// 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, MetricId};
+
+use crate::ipc::need_ipc;
+
+/// Developer-facing API for recording URL metrics.
+///
+/// Instances of this class type are automatically generated by the parsers
+/// at build time, allowing developers to record values that were previously
+/// registered in the metrics.yaml file.
+#[derive(Clone)]
+pub enum UrlMetric {
+ Parent(glean::private::UrlMetric),
+ Child(UrlMetricIpc),
+}
+#[derive(Clone, Debug)]
+pub struct UrlMetricIpc;
+
+impl UrlMetric {
+ /// Create a new Url metric.
+ pub fn new(_id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ UrlMetric::Child(UrlMetricIpc)
+ } else {
+ UrlMetric::Parent(glean::private::UrlMetric::new(meta))
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ UrlMetric::Parent(_) => UrlMetric::Child(UrlMetricIpc),
+ UrlMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl glean::traits::Url for UrlMetric {
+ pub fn set<S: Into<std::string::String>>(&self, value: S) {
+ match self {
+ UrlMetric::Parent(p) => p.set(value),
+ UrlMetric::Child(_) => {
+ log::error!(
+ "Unable to set Url metric in non-main process. This operation will be ignored."
+ );
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set URL metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ }
+ };
+ }
+
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<std::string::String> {
+ let ping_name = ping_name.into().map(|s| s.to_string());
+ match self {
+ UrlMetric::Parent(p) => p.test_get_value(ping_name),
+ UrlMetric::Child(_) => {
+ panic!("Cannot get test value for Url metric in non-main process!")
+ }
+ }
+ }
+
+ pub fn test_get_num_recorded_errors(&self, error: glean::ErrorType) -> i32 {
+ match self {
+ UrlMetric::Parent(p) => p.test_get_num_recorded_errors(error),
+ UrlMetric::Child(_) => panic!(
+ "Cannot get the number of recorded errors for Url metric in non-main process!"
+ ),
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_url_value() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_url;
+
+ metric.set("https://example.com");
+
+ assert_eq!(
+ "https://example.com",
+ metric.test_get_value("store1").unwrap()
+ );
+ }
+
+ #[test]
+ fn url_ipc() {
+ // UrlMetric doesn't support IPC.
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_url;
+
+ parent_metric.set("https://example.com/parent");
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ let _raii = ipc::test_set_need_ipc(true);
+
+ // Instrumentation calls do not panic.
+ child_metric.set("https://example.com/child");
+
+ // (They also shouldn't do anything,
+ // but that's not something we can inspect in this test)
+ }
+
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert!(
+ "https://example.com/parent" == parent_metric.test_get_value("store1").unwrap(),
+ "Url metrics should only work in the parent process"
+ );
+ }
+}
diff --git a/toolkit/components/glean/api/src/private/uuid.rs b/toolkit/components/glean/api/src/private/uuid.rs
new file mode 100644
index 0000000000..101a1a9875
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/uuid.rs
@@ -0,0 +1,171 @@
+// 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 uuid::Uuid;
+
+use super::{CommonMetricData, MetricId};
+
+use crate::ipc::need_ipc;
+
+/// A UUID metric.
+///
+/// Stores UUID values.
+pub enum UuidMetric {
+ Parent(glean::private::UuidMetric),
+ Child(UuidMetricIpc),
+}
+
+#[derive(Debug)]
+pub struct UuidMetricIpc;
+
+impl UuidMetric {
+ /// Create a new UUID metric.
+ pub fn new(_id: MetricId, meta: CommonMetricData) -> Self {
+ if need_ipc() {
+ UuidMetric::Child(UuidMetricIpc)
+ } else {
+ UuidMetric::Parent(glean::private::UuidMetric::new(meta))
+ }
+ }
+
+ #[cfg(test)]
+ pub(crate) fn child_metric(&self) -> Self {
+ match self {
+ UuidMetric::Parent(_) => UuidMetric::Child(UuidMetricIpc),
+ UuidMetric::Child(_) => panic!("Can't get a child metric from a child metric"),
+ }
+ }
+}
+
+#[inherent]
+impl glean::traits::Uuid for UuidMetric {
+ /// Set to the specified value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `value` - The UUID to set the metric to.
+ pub fn set(&self, value: Uuid) {
+ match self {
+ UuidMetric::Parent(p) => p.set(value.to_string()),
+ UuidMetric::Child(_c) => {
+ log::error!("Unable to set the uuid metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set uuid metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ }
+ };
+ }
+
+ /// Generate a new random UUID and set the metric to it.
+ ///
+ /// ## Return value
+ ///
+ /// Returns the stored UUID value or `Uuid::nil` if called from
+ /// a non-main process.
+ pub fn generate_and_set(&self) -> Uuid {
+ match self {
+ UuidMetric::Parent(p) => Uuid::parse_str(&p.generate_and_set()).unwrap(),
+ UuidMetric::Child(_c) => {
+ log::error!("Unable to set the uuid metric in non-main process. This operation will be ignored.");
+ // If we're in automation we can panic so the instrumentor knows they've gone wrong.
+ // This is a deliberate violation of Glean's "metric APIs must not throw" design.
+ assert!(!crate::ipc::is_in_automation(), "Attempted to set uuid metric in non-main process, which is forbidden. This panics in automation.");
+ // TODO: Record an error.
+ Uuid::nil()
+ }
+ }
+ }
+
+ /// **Test-only API.**
+ ///
+ /// Get the stored UUID value.
+ /// This doesn't clear the stored value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `storage_name` - the storage name to look into.
+ ///
+ /// ## Return value
+ ///
+ /// Returns the stored value or `None` if nothing stored.
+ pub fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, storage_name: S) -> Option<Uuid> {
+ let storage_name = storage_name.into().map(|s| s.to_string());
+ match self {
+ UuidMetric::Parent(p) => p
+ .test_get_value(storage_name)
+ .and_then(|s| Uuid::parse_str(&s).ok()),
+ UuidMetric::Child(_c) => panic!("Cannot get test value for in non-main process!"),
+ }
+ }
+
+ /// **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: glean::ErrorType) -> i32 {
+ match self {
+ UuidMetric::Parent(p) => p.test_get_num_recorded_errors(error),
+ UuidMetric::Child(_c) => {
+ panic!("Cannot get test value for UuidMetric in non-main process!")
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::{common_test::*, ipc, metrics};
+
+ #[test]
+ fn sets_uuid_value() {
+ let _lock = lock_test();
+
+ let metric = &metrics::test_only_ipc::a_uuid;
+ let expected = Uuid::new_v4();
+ metric.set(expected.clone());
+
+ assert_eq!(expected, metric.test_get_value("store1").unwrap());
+ }
+
+ #[test]
+ fn uuid_ipc() {
+ // UuidMetric doesn't support IPC.
+ let _lock = lock_test();
+
+ let parent_metric = &metrics::test_only_ipc::a_uuid;
+ let expected = Uuid::new_v4();
+ parent_metric.set(expected.clone());
+
+ {
+ let child_metric = parent_metric.child_metric();
+
+ // Instrumentation calls do not panic.
+ child_metric.set(Uuid::new_v4());
+
+ // (They also shouldn't do anything,
+ // but that's not something we can inspect in this test)
+ }
+
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+
+ assert_eq!(
+ expected,
+ parent_metric.test_get_value("store1").unwrap(),
+ "UUID metrics should only work in the parent process"
+ );
+ }
+}