summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/glean')
-rw-r--r--toolkit/components/glean/Cargo.toml23
-rw-r--r--toolkit/components/glean/api/Cargo.toml26
-rw-r--r--toolkit/components/glean/api/src/common_test.rs51
-rw-r--r--toolkit/components/glean/api/src/ffi/event.rs90
-rw-r--r--toolkit/components/glean/api/src/ffi/macros.rs61
-rw-r--r--toolkit/components/glean/api/src/ffi/mod.rs268
-rw-r--r--toolkit/components/glean/api/src/ipc.rs127
-rw-r--r--toolkit/components/glean/api/src/lib.rs23
-rw-r--r--toolkit/components/glean/api/src/metrics.rs70
-rw-r--r--toolkit/components/glean/api/src/pings.rs15
-rw-r--r--toolkit/components/glean/api/src/private/boolean.rs128
-rw-r--r--toolkit/components/glean/api/src/private/counter.rs188
-rw-r--r--toolkit/components/glean/api/src/private/datetime.rs241
-rw-r--r--toolkit/components/glean/api/src/private/event.rs237
-rw-r--r--toolkit/components/glean/api/src/private/labeled.rs333
-rw-r--r--toolkit/components/glean/api/src/private/memory_distribution.rs193
-rw-r--r--toolkit/components/glean/api/src/private/mod.rs90
-rw-r--r--toolkit/components/glean/api/src/private/ping.rs89
-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/timespan.rs134
-rw-r--r--toolkit/components/glean/api/src/private/timing_distribution.rs273
-rw-r--r--toolkit/components/glean/api/src/private/uuid.rs168
-rw-r--r--toolkit/components/glean/bindings/Category.cpp71
-rw-r--r--toolkit/components/glean/bindings/Category.h42
-rw-r--r--toolkit/components/glean/bindings/Glean.cpp75
-rw-r--r--toolkit/components/glean/bindings/Glean.h38
-rw-r--r--toolkit/components/glean/bindings/GleanPings.cpp77
-rw-r--r--toolkit/components/glean/bindings/GleanPings.h38
-rw-r--r--toolkit/components/glean/bindings/MetricTypes.h19
-rw-r--r--toolkit/components/glean/bindings/private/Boolean.cpp38
-rw-r--r--toolkit/components/glean/bindings/private/Boolean.h74
-rw-r--r--toolkit/components/glean/bindings/private/Common.cpp32
-rw-r--r--toolkit/components/glean/bindings/private/Common.h25
-rw-r--r--toolkit/components/glean/bindings/private/Counter.cpp36
-rw-r--r--toolkit/components/glean/bindings/private/Counter.h73
-rw-r--r--toolkit/components/glean/bindings/private/Datetime.cpp46
-rw-r--r--toolkit/components/glean/bindings/private/Datetime.h91
-rw-r--r--toolkit/components/glean/bindings/private/DistributionData.h20
-rw-r--r--toolkit/components/glean/bindings/private/Event.cpp91
-rw-r--r--toolkit/components/glean/bindings/private/Event.h113
-rw-r--r--toolkit/components/glean/bindings/private/MemoryDistribution.cpp64
-rw-r--r--toolkit/components/glean/bindings/private/MemoryDistribution.h90
-rw-r--r--toolkit/components/glean/bindings/private/Ping.cpp24
-rw-r--r--toolkit/components/glean/bindings/private/Ping.h68
-rw-r--r--toolkit/components/glean/bindings/private/String.cpp38
-rw-r--r--toolkit/components/glean/bindings/private/String.h80
-rw-r--r--toolkit/components/glean/bindings/private/StringList.cpp46
-rw-r--r--toolkit/components/glean/bindings/private/StringList.h96
-rw-r--r--toolkit/components/glean/bindings/private/Timespan.cpp42
-rw-r--r--toolkit/components/glean/bindings/private/Timespan.h85
-rw-r--r--toolkit/components/glean/bindings/private/TimingDistribution.cpp79
-rw-r--r--toolkit/components/glean/bindings/private/TimingDistribution.h109
-rw-r--r--toolkit/components/glean/bindings/private/Uuid.cpp44
-rw-r--r--toolkit/components/glean/bindings/private/Uuid.h82
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/cpp.py112
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/js.py254
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py86
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/rust.py227
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/string_table.py97
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja255
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp_pings.jinja230
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/js.jinja2129
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/js_pings.jinja264
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2217
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja247
-rw-r--r--toolkit/components/glean/build_scripts/glean_parser_ext/util.py76
-rw-r--r--toolkit/components/glean/cbindgen.toml19
-rw-r--r--toolkit/components/glean/docs/api.md4
-rw-r--r--toolkit/components/glean/docs/code_organization.md64
-rw-r--r--toolkit/components/glean/docs/images/fog-modules.svg6
-rw-r--r--toolkit/components/glean/docs/index.md32
-rw-r--r--toolkit/components/glean/docs/ipc.md106
-rw-r--r--toolkit/components/glean/docs/new_definitions_file.md85
-rw-r--r--toolkit/components/glean/docs/new_metric_types.md114
-rw-r--r--toolkit/components/glean/docs/preferences.md21
-rw-r--r--toolkit/components/glean/docs/storage.md14
-rw-r--r--toolkit/components/glean/docs/style_guide.md58
-rw-r--r--toolkit/components/glean/docs/testing.md117
-rw-r--r--toolkit/components/glean/docs/updating_parser.md37
-rw-r--r--toolkit/components/glean/docs/updating_sdk.md39
-rw-r--r--toolkit/components/glean/gtest/Cargo.toml12
-rw-r--r--toolkit/components/glean/gtest/TestFog.cpp220
-rw-r--r--toolkit/components/glean/gtest/TestFogIPC.cpp47
-rw-r--r--toolkit/components/glean/gtest/moz.build13
-rw-r--r--toolkit/components/glean/gtest/test.rs44
-rw-r--r--toolkit/components/glean/ipc/FOGIPC.cpp99
-rw-r--r--toolkit/components/glean/ipc/FOGIPC.h56
-rw-r--r--toolkit/components/glean/metrics.yaml109
-rw-r--r--toolkit/components/glean/metrics_index.py19
-rw-r--r--toolkit/components/glean/moz.build160
-rw-r--r--toolkit/components/glean/pings.yaml29
-rw-r--r--toolkit/components/glean/pytest/metrics_expires_number_test.yaml22
-rw-r--r--toolkit/components/glean/pytest/metrics_expires_versions_test.yaml84
-rw-r--r--toolkit/components/glean/pytest/metrics_test.yaml270
-rw-r--r--toolkit/components/glean/pytest/metrics_test_output542
-rw-r--r--toolkit/components/glean/pytest/metrics_test_output_cpp130
-rw-r--r--toolkit/components/glean/pytest/metrics_test_output_js255
-rw-r--r--toolkit/components/glean/pytest/pings_test.yaml116
-rw-r--r--toolkit/components/glean/pytest/pings_test_output93
-rw-r--r--toolkit/components/glean/pytest/pings_test_output_cpp62
-rw-r--r--toolkit/components/glean/pytest/pings_test_output_js99
-rw-r--r--toolkit/components/glean/pytest/python.ini6
-rw-r--r--toolkit/components/glean/pytest/test_glean_parser_cpp.py77
-rw-r--r--toolkit/components/glean/pytest/test_glean_parser_js.py75
-rw-r--r--toolkit/components/glean/pytest/test_glean_parser_rust.py113
-rw-r--r--toolkit/components/glean/sphinx/glean.py40
-rw-r--r--toolkit/components/glean/src/lib.rs364
-rw-r--r--toolkit/components/glean/src/viaduct_uploader.rs54
-rw-r--r--toolkit/components/glean/test_metrics.yaml353
-rw-r--r--toolkit/components/glean/test_pings.yaml25
-rw-r--r--toolkit/components/glean/xpcom/FOG.cpp53
-rw-r--r--toolkit/components/glean/xpcom/FOG.h27
-rw-r--r--toolkit/components/glean/xpcom/components.conf16
-rw-r--r--toolkit/components/glean/xpcom/moz.build28
-rw-r--r--toolkit/components/glean/xpcom/nsIFOG.idl50
-rw-r--r--toolkit/components/glean/xpcom/nsIGleanMetrics.idl394
-rw-r--r--toolkit/components/glean/xpcshell/test_Glean.js221
-rw-r--r--toolkit/components/glean/xpcshell/xpcshell.ini4
119 files changed, 11662 insertions, 0 deletions
diff --git a/toolkit/components/glean/Cargo.toml b/toolkit/components/glean/Cargo.toml
new file mode 100644
index 0000000000..0e8094a517
--- /dev/null
+++ b/toolkit/components/glean/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "fog_control"
+version = "0.1.0"
+authors = ["Glean SDK team <glean-team@mozilla.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[dependencies]
+glean = "33.10.2"
+glean-core = { version = "33.10.2", features = ["rkv-safe-mode"] }
+log = "0.4"
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+static_prefs = { path = "../../../modules/libpref/init/static_prefs" }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+once_cell = "1.2.0"
+fog = { path = "./api" }
+cstr = "0.2"
+viaduct = { git = "https://github.com/mozilla/application-services", rev = "8a576fbe79199fa8664f64285524017f74ebcc5f" } # Copied from toolkit/library/rust/shared/Cargo.toml
+url = "2.1" # Copied from viaduct's deps, see https://github.com/mozilla/application-services/issues/3062
+
+[features]
+with_gecko = ["fog/with_gecko"]
diff --git a/toolkit/components/glean/api/Cargo.toml b/toolkit/components/glean/api/Cargo.toml
new file mode 100644
index 0000000000..8c72c50823
--- /dev/null
+++ b/toolkit/components/glean/api/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "fog"
+version = "0.1.0"
+authors = ["Glean SDK team <glean-team@mozilla.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+bincode = "1.0"
+chrono = "0.4.10"
+glean = "33.10.2"
+glean-core = { version = "33.10.2", features = ["rkv-safe-mode"] }
+inherent = "0.1.4"
+log = "0.4"
+nsstring = { path = "../../../../xpcom/rust/nsstring", optional = true }
+once_cell = "1.2.0"
+serde = { version = "1.0", features = ["derive"] }
+uuid = { version = "0.8.1", features = ["v4"] }
+xpcom = { path = "../../../../xpcom/rust/xpcom", optional = true }
+thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
+
+[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..c88ae88cb4
--- /dev/null
+++ b/toolkit/components/glean/api/src/common_test.rs
@@ -0,0 +1,51 @@
+// 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().display().to_string();
+
+ let cfg = glean::Configuration {
+ upload_enabled: true,
+ data_path: tmpname,
+ application_id: GLOBAL_APPLICATION_ID.into(),
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: None,
+ server_endpoint: None,
+ uploader: None,
+ };
+
+ let client_info = glean::ClientInfoMetrics {
+ app_build: "test-build".into(),
+ app_display_version: "1.2.3".into(),
+ };
+
+ glean::test_reset_glean(cfg, client_info, true);
+
+ dir
+}
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..6a433eda54
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/event.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, 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<i32>,
+ extra_values: &ThinVec<nsCString>,
+) {
+ // If no extra keys are passed, we can shortcut here.
+ if extra_keys.is_empty() {
+ if metric_maps::event_record_wrapper(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, v.to_string()))
+ .collect();
+ match metric_maps::event_record_wrapper(id, extra) {
+ Ok(()) => {}
+ Err(EventRecordingError::InvalidId) => panic!("No event for id {}", id),
+ Err(EventRecordingError::InvalidExtraKey) => {
+ panic!("Invalid extra keys in map for id {}", id)
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_event_record_str(
+ 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 metric_maps::event_record_wrapper_str(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();
+ match metric_maps::event_record_wrapper_str(id, extra) {
+ Ok(()) => {}
+ Err(EventRecordingError::InvalidId) => panic!("No event for id {}", id),
+ Err(EventRecordingError::InvalidExtraKey) => {
+ panic!("Invalid extra keys in map for id {}", id)
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_event_test_has_value(id: u32, storage_name: &nsACString) -> bool {
+ metric_maps::event_test_get_value_wrapper(id, &storage_name.to_utf8()).is_some()
+}
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..c6776e96af
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/macros.rs
@@ -0,0 +1,61 @@
+// 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.
+///
+/// # Arguments
+///
+/// * `$map` - The name of the hash map within `metrics::__glean_metric_maps`
+/// as generated by glean_parser.
+/// * `$id` - The ID of the metric to get.
+macro_rules! metric_get {
+ ($map:ident, $id:ident) => {
+ match $crate::metrics::__glean_metric_maps::$map.get(&$id.into()) {
+ Some(metric) => metric,
+ None => panic!("No metric for id {}", $id),
+ }
+ };
+}
+
+/// Test whether a value is stored for the metric identified by its ID.
+///
+/// # Arguments
+///
+/// * `$map` - The name of the hash map within `metrics::__glean_metric_maps`
+/// as generated by glean_parser.
+/// * `$id` - The ID of the metric to get.
+/// * `$storage` - the storage name to look into.
+macro_rules! test_has {
+ ($map:ident, $id:ident, $storage:ident) => {{
+ let metric = metric_get!($map, $id);
+ 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 metric identified by its ID.
+///
+/// # Arguments
+///
+/// * `$map` - The name of the hash map within `metrics::__glean_metric_maps`
+/// as generated by glean_parser.
+/// * `$id` - The ID of the metric to get.
+/// * `$storage` - the storage name to look into.
+macro_rules! test_get {
+ ($map:ident, $id:ident, $storage:ident) => {{
+ let metric = metric_get!($map, $id);
+ let storage = if $storage.is_empty() {
+ None
+ } else {
+ Some($storage.to_utf8())
+ };
+ metric.test_get_value(storage.as_deref()).unwrap()
+ }};
+}
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..b5ba32b430
--- /dev/null
+++ b/toolkit/components/glean/api/src/ffi/mod.rs
@@ -0,0 +1,268 @@
+// 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, nsCString};
+use thin_vec::ThinVec;
+use uuid::Uuid;
+
+#[macro_use]
+mod macros;
+mod event;
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_counter_add(id: u32, amount: i32) {
+ let metric = metric_get!(COUNTER_MAP, id);
+ metric.add(amount);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_counter_test_has_value(id: u32, storage_name: &nsACString) -> bool {
+ test_has!(COUNTER_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_counter_test_get_value(id: u32, storage_name: &nsACString) -> i32 {
+ test_get!(COUNTER_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_timespan_start(id: u32) {
+ let metric = metric_get!(TIMESPAN_MAP, id);
+ metric.start();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_timespan_stop(id: u32) {
+ let metric = metric_get!(TIMESPAN_MAP, id);
+ metric.stop();
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_timespan_test_has_value(id: u32, storage_name: &nsACString) -> bool {
+ test_has!(TIMESPAN_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_timespan_test_get_value(id: u32, storage_name: &nsACString) -> u64 {
+ test_get!(TIMESPAN_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_boolean_test_has_value(id: u32, storage_name: &nsACString) -> bool {
+ test_has!(BOOLEAN_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_boolean_test_get_value(id: u32, storage_name: &nsACString) -> bool {
+ test_get!(BOOLEAN_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub extern "C" fn fog_boolean_set(id: u32, value: bool) {
+ let metric = metric_get!(BOOLEAN_MAP, id);
+ metric.set(value);
+}
+
+// The String functions are custom because test_get needs to use an outparam.
+// If we can make test_get optional, we can go back to using the macro to
+// generate the rest of the functions, or something.
+
+#[no_mangle]
+pub extern "C" fn fog_string_test_has_value(id: u32, storage_name: &nsACString) -> bool {
+ test_has!(STRING_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_test_get_value(
+ id: u32,
+ storage_name: &nsACString,
+ value: &mut nsACString,
+) {
+ let val = test_get!(STRING_MAP, id, storage_name);
+ value.assign(&val);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_set(id: u32, value: &nsACString) {
+ let metric = metric_get!(STRING_MAP, id);
+ metric.set(value.to_utf8());
+}
+
+// String List Functions:
+
+#[no_mangle]
+pub extern "C" fn fog_string_list_test_has_value(id: u32, storage_name: &nsACString) -> bool {
+ test_has!(STRING_LIST_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_list_test_get_value(
+ id: u32,
+ storage_name: &nsACString,
+ value: &mut ThinVec<nsCString>,
+) {
+ let val = test_get!(STRING_LIST_MAP, id, storage_name);
+ for v in val {
+ value.push(v.into());
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_list_add(id: u32, value: &nsACString) {
+ let metric = metric_get!(STRING_LIST_MAP, id);
+ metric.add(value.to_utf8());
+}
+
+#[no_mangle]
+pub extern "C" fn fog_string_list_set(id: u32, value: &ThinVec<nsCString>) {
+ let metric = metric_get!(STRING_LIST_MAP, id);
+ let value = value.iter().map(|s| s.to_utf8().into()).collect();
+ metric.set(value);
+}
+
+// The Uuid functions are custom because test_get needs to use an outparam.
+// If we can make test_get optional, we can go back to using the macro to
+// generate the rest of the functions, or something.
+
+#[no_mangle]
+pub extern "C" fn fog_uuid_test_has_value(id: u32, storage_name: &nsACString) -> bool {
+ test_has!(UUID_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub extern "C" fn fog_uuid_test_get_value(
+ id: u32,
+ storage_name: &nsACString,
+ value: &mut nsACString,
+) {
+ let uuid = test_get!(UUID_MAP, id, storage_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()) {
+ let metric = metric_get!(UUID_MAP, id);
+ metric.set(uuid);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_uuid_generate_and_set(id: u32) {
+ let metric = metric_get!(UUID_MAP, id);
+ metric.generate_and_set();
+}
+
+#[no_mangle]
+pub extern "C" fn fog_datetime_test_has_value(id: u32, storage_name: &nsACString) -> bool {
+ test_has!(DATETIME_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub extern "C" fn fog_datetime_test_get_value(
+ id: u32,
+ storage_name: &nsACString,
+ value: &mut nsACString,
+) {
+ let val = test_get!(DATETIME_MAP, id, storage_name);
+ value.assign(&val.to_rfc3339());
+}
+
+#[no_mangle]
+pub extern "C" fn fog_datetime_set(
+ id: u32,
+ year: i32,
+ month: u32,
+ day: u32,
+ hour: u32,
+ minute: u32,
+ second: u32,
+ nano: u32,
+ offset_seconds: i32,
+) {
+ let metric = metric_get!(DATETIME_MAP, id);
+ metric.set_with_details(year, month, day, hour, minute, second, nano, offset_seconds);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_memory_distribution_test_has_value(
+ id: u32,
+ storage_name: &nsACString,
+) -> bool {
+ test_has!(MEMORY_DISTRIBUTION_MAP, id, storage_name)
+}
+
+#[no_mangle]
+pub extern "C" fn fog_memory_distribution_test_get_value(
+ id: u32,
+ storage_name: &nsACString,
+ sum: &mut u64,
+ buckets: &mut ThinVec<u64>,
+ counts: &mut ThinVec<u64>,
+) {
+ let val = test_get!(MEMORY_DISTRIBUTION_MAP, id, storage_name);
+ *sum = val.sum;
+ for (&bucket, &count) in val.values.iter() {
+ buckets.push(bucket);
+ counts.push(count);
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn fog_memory_distribution_accumulate(id: u32, sample: u64) {
+ let metric = metric_get!(MEMORY_DISTRIBUTION_MAP, id);
+ metric.accumulate(sample);
+}
+
+#[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());
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_start(id: u32) -> u64 {
+ let metric = metric_get!(TIMING_DISTRIBUTION_MAP, id);
+ metric.start()
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_stop_and_accumulate(id: u32, timing_id: u64) {
+ let metric = metric_get!(TIMING_DISTRIBUTION_MAP, id);
+ metric.stop_and_accumulate(timing_id);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_cancel(id: u32, timing_id: u64) {
+ let metric = metric_get!(TIMING_DISTRIBUTION_MAP, id);
+ metric.cancel(timing_id);
+}
+
+#[no_mangle]
+pub extern "C" fn fog_timing_distribution_test_has_value(id: u32, ping_name: &nsACString) -> bool {
+ test_has!(TIMING_DISTRIBUTION_MAP, id, 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 = test_get!(TIMING_DISTRIBUTION_MAP, id, ping_name);
+ *sum = val.sum;
+ for (&bucket, &count) in val.values.iter() {
+ buckets.push(bucket);
+ counts.push(count);
+ }
+}
diff --git a/toolkit/components/glean/api/src/ipc.rs b/toolkit/components/glean/api/src/ipc.rs
new file mode 100644
index 0000000000..1b6992d7b5
--- /dev/null
+++ b/toolkit/components/glean/api/src/ipc.rs
@@ -0,0 +1,127 @@
+// 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::{Instant, MetricId};
+use once_cell::sync::Lazy;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+#[cfg(not(feature = "with_gecko"))]
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Mutex;
+#[cfg(feature = "with_gecko")]
+use {
+ std::sync::atomic::{AtomicU32, Ordering},
+ xpcom::interfaces::nsIXULRuntime,
+};
+
+use super::metrics::__glean_metric_maps;
+
+type EventRecord = (Instant, Option<HashMap<i32, 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 events: HashMap<MetricId, Vec<EventRecord>>,
+ pub memory_samples: HashMap<MetricId, Vec<u64>>,
+ pub string_lists: HashMap<MetricId, Vec<String>>,
+ pub timing_samples: HashMap<MetricId, Vec<u128>>,
+}
+
+/// Global singleton: pending IPC payload.
+static PAYLOAD: Lazy<Mutex<IPCPayload>> = Lazy::new(|| Mutex::new(IPCPayload::default()));
+
+pub fn with_ipc_payload<F, R>(f: F) -> R
+where
+ F: FnOnce(&mut IPCPayload) -> R,
+{
+ 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(|| {
+ if let Some(appinfo) = xpcom::services::get_XULRuntime() {
+ let mut process_type = nsIXULRuntime::PROCESS_TYPE_DEFAULT as u32;
+ let rv = unsafe { appinfo.GetProcessType(&mut process_type) };
+ if rv.succeeded() {
+ return AtomicU32::new(process_type);
+ }
+ }
+ AtomicU32::new(nsIXULRuntime::PROCESS_TYPE_DEFAULT as u32)
+});
+
+#[cfg(feature = "with_gecko")]
+pub fn need_ipc() -> bool {
+ PROCESS_TYPE.load(Ordering::Relaxed) != nsIXULRuntime::PROCESS_TYPE_DEFAULT as u32
+}
+
+/// 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
+ })
+}
+
+pub fn replay_from_buf(buf: &[u8]) -> Result<(), ()> {
+ let ipc_payload: IPCPayload = bincode::deserialize(buf).map_err(|_| ())?;
+ for (id, value) in ipc_payload.counters.into_iter() {
+ log::info!("Asked to replay {:?}, {:?}", id, value);
+ if let Some(metric) = __glean_metric_maps::COUNTER_MAP.get(&id) {
+ metric.add(value);
+ }
+ }
+ 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..e2cd29c7af
--- /dev/null
+++ b/toolkit/components/glean/api/src/lib.rs
@@ -0,0 +1,23 @@
+// 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};
+
+pub mod metrics;
+pub mod pings;
+pub mod private;
+
+pub mod ipc;
+
+#[cfg(test)]
+mod common_test;
+mod ffi;
diff --git a/toolkit/components/glean/api/src/metrics.rs b/toolkit/components/glean/api/src/metrics.rs
new file mode 100644
index 0000000000..9c2860bc29
--- /dev/null
+++ b/toolkit/components/glean/api/src/metrics.rs
@@ -0,0 +1,70 @@
+// 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
+//! 'toolkit/components/glean/metrics.yaml`.
+
+#[cfg(not(feature = "cargo-clippy"))]
+include!(concat!(
+ env!("MOZ_TOPOBJDIR"),
+ "/toolkit/components/glean/api/src/metrics.rs"
+));
+
+// When running clippy the linter, `MOZ_TOPOBJDIR` is not set
+// (and the `metrics.rs` file might not even be generated yet),
+// so we need to manually define some things we expect from it so the rest of the build can assume
+// it's there.
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=1674726.
+#[cfg(feature = "cargo-clippy")]
+#[allow(dead_code)]
+pub(crate) mod __glean_metric_maps {
+ use std::collections::HashMap;
+
+ use crate::private::*;
+ use once_cell::sync::Lazy;
+
+ pub static TIMESPAN_MAP: Lazy<HashMap<MetricId, &Lazy<TimespanMetric>>> =
+ Lazy::new(HashMap::new);
+
+ pub static COUNTER_MAP: Lazy<HashMap<MetricId, &Lazy<CounterMetric>>> = Lazy::new(HashMap::new);
+
+ pub static BOOLEAN_MAP: Lazy<HashMap<MetricId, &Lazy<BooleanMetric>>> = Lazy::new(HashMap::new);
+
+ pub static DATETIME_MAP: Lazy<HashMap<MetricId, &Lazy<DatetimeMetric>>> =
+ Lazy::new(HashMap::new);
+
+ pub static STRING_MAP: Lazy<HashMap<MetricId, &Lazy<StringMetric>>> = Lazy::new(HashMap::new);
+
+ pub static MEMORY_DISTRIBUTION_MAP: Lazy<HashMap<MetricId, &Lazy<MemoryDistributionMetric>>> =
+ Lazy::new(HashMap::new);
+
+ pub static STRING_LIST_MAP: Lazy<HashMap<MetricId, &Lazy<StringListMetric>>> =
+ Lazy::new(HashMap::new);
+
+ pub static UUID_MAP: Lazy<HashMap<MetricId, &Lazy<UuidMetric>>> = Lazy::new(HashMap::new);
+
+ pub(crate) fn event_record_wrapper(
+ _metric_id: u32,
+ _extra: Option<HashMap<i32, String>>,
+ ) -> Result<(), EventRecordingError> {
+ Err(EventRecordingError::InvalidId)
+ }
+
+ pub(crate) fn event_record_wrapper_str(
+ _metric_id: u32,
+ _extra: Option<HashMap<String, String>>,
+ ) -> Result<(), EventRecordingError> {
+ Err(EventRecordingError::InvalidId)
+ }
+
+ pub(crate) fn event_test_get_value_wrapper(
+ _metric_id: u32,
+ _storage_name: &str,
+ ) -> Option<Vec<RecordedEvent>> {
+ None
+ }
+}
diff --git a/toolkit/components/glean/api/src/pings.rs b/toolkit/components/glean/api/src/pings.rs
new file mode 100644
index 0000000000..5837278566
--- /dev/null
+++ b/toolkit/components/glean/api/src/pings.rs
@@ -0,0 +1,15 @@
+// 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`.
+
+#[cfg(not(feature = "cargo-clippy"))]
+include!(concat!(
+ env!("MOZ_TOPOBJDIR"),
+ "/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..8fd6dbbd0f
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/boolean.rs
@@ -0,0 +1,128 @@
+// 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_core::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(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(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(pub)]
+impl Boolean for BooleanMetric {
+ /// Set to the specified boolean value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `value` - the value to set.
+ fn set(&self, value: bool) {
+ match self {
+ BooleanMetric::Parent(p) => {
+ Boolean::set(&*p, value);
+ }
+ BooleanMetric::Child(_) => {
+ log::error!("Unable to set boolean metric in non-parent process. Ignoring.");
+ // 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.
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<bool> {
+ match self {
+ BooleanMetric::Parent(p) => p.test_get_value(ping_name),
+ BooleanMetric::Child(_) => {
+ panic!("Cannot get test value for boolean metric in non-parent 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..207589adaf
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/counter.rs
@@ -0,0 +1,188 @@
+// 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_core::traits::Counter;
+
+use super::CommonMetricData;
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+use crate::private::MetricId;
+
+/// 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: 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 = glean::private::CounterMetric::new(meta);
+ CounterMetric::Parent { id, 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(pub)]
+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.
+ fn add(&self, amount: i32) {
+ match self {
+ CounterMetric::Parent { inner, .. } => {
+ Counter::add(&*inner, 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.
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<i32> {
+ 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.
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: glean::ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ match self {
+ CounterMetric::Parent { inner, .. } => {
+ inner.test_get_num_recorded_errors(error, ping_name)
+ }
+ 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/datetime.rs b/toolkit/components/glean/api/src/private/datetime.rs
new file mode 100644
index 0000000000..acaafdedd9
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/datetime.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 inherent::inherent;
+
+use super::{CommonMetricData, MetricId};
+
+use super::TimeUnit;
+use crate::ipc::need_ipc;
+use chrono::{FixedOffset, TimeZone};
+use glean_core::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)),
+ _ => {
+ log::error!("Unable to construct datetime")
+ // TODO: Record an error
+ }
+ }
+ }
+ DatetimeMetric::Child(_) => {
+ log::error!("Unable to set datetime metric in non-parent process. Ignoring.");
+ // TODO: Record an error.
+ }
+ }
+ }
+}
+
+#[inherent(pub)]
+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.
+ fn set(&self, value: Option<glean_core::metrics::Datetime>) {
+ match self {
+ DatetimeMetric::Parent(p) => {
+ Datetime::set(&*p, value);
+ }
+ DatetimeMetric::Child(_) => {
+ log::error!(
+ "Unable to set datetime metric DatetimeMetric in non-parent process. Ignoring."
+ );
+ // 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`.
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<glean_core::metrics::Datetime> {
+ match self {
+ DatetimeMetric::Parent(p) => p.test_get_value(ping_name),
+ DatetimeMetric::Child(_) => {
+ panic!("Cannot get test value for DatetimeMetric in non-parent 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.
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: glean::ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ match self {
+ DatetimeMetric::Parent(p) => p.test_get_num_recorded_errors(error, ping_name),
+ DatetimeMetric::Child(_) => panic!("Cannot get the number of recorded errors for DatetimeMetric in non-parent 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));
+
+ assert_eq!(
+ DateTime::parse_from_rfc3339("2020-05-07T11:58:00+05:00").unwrap(),
+ 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);
+
+ assert_eq!(
+ DateTime::parse_from_rfc3339("2020-05-07T11:58:00+05:00").unwrap(),
+ 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));
+
+ {
+ 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));
+
+ // (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!(
+ DateTime::parse_from_rfc3339("2020-10-13T16:41:00+05:00").unwrap(),
+ parent_metric.test_get_value("store1").unwrap()
+ );
+ }
+}
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..306376c269
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/event.rs
@@ -0,0 +1,237 @@
+// 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, Instant, MetricId, RecordedEvent};
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+
+use glean_core::traits::Event;
+pub use glean_core::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 }
+ }
+ }
+
+ #[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"),
+ }
+ }
+}
+
+#[inherent(pub)]
+impl<K: 'static + ExtraKeys + Send + Sync> Event for EventMetric<K> {
+ type Extra = K;
+
+ fn record<M: Into<Option<HashMap<K, String>>>>(&self, extra: M) {
+ match self {
+ EventMetric::Parent { inner, .. } => {
+ inner.record(extra);
+ }
+ EventMetric::Child(c) => {
+ let extra = extra.into().map(|hash_map| {
+ hash_map
+ .iter()
+ .map(|(k, v)| (k.index(), v.clone()))
+ .collect()
+ });
+ let now = Instant::now();
+ with_ipc_payload(move |payload| {
+ if let Some(v) = payload.events.get_mut(&c.0) {
+ v.push((now, extra));
+ } else {
+ let mut v = vec![];
+ v.push((now, extra));
+ payload.events.insert(c.0, v);
+ }
+ });
+ }
+ }
+ }
+
+ 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-parent process!",)
+ }
+ }
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: glean::ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ match self {
+ EventMetric::Parent { inner, .. } => {
+ inner.test_get_num_recorded_errors(error, ping_name)
+ }
+ EventMetric::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]
+ #[ignore] // TODO: Enable them back when bug 1673668 lands.
+ 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]
+ #[ignore] // TODO: Enable them back when bug 1673668 lands.
+ fn smoke_test_event_with_extra() {
+ let _lock = lock_test();
+
+ #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
+ enum TestKeys {
+ Extra1,
+ }
+
+ impl ExtraKeys for TestKeys {
+ const ALLOWED_KEYS: &'static [&'static str] = &["extra1"];
+
+ fn index(self) -> i32 {
+ self as i32
+ }
+ }
+
+ let metric = EventMetric::<TestKeys>::new(
+ 0.into(),
+ CommonMetricData {
+ name: "event_metric_with_extra".into(),
+ category: "telemetry".into(),
+ send_in_pings: vec!["store1".into()],
+ disabled: false,
+ ..Default::default()
+ },
+ );
+
+ // No extra keys
+ metric.record(None);
+
+ // A valid extra key
+ let mut map = HashMap::new();
+ map.insert(TestKeys::Extra1, "a-valid-value".into());
+ metric.record(map);
+
+ let recorded = metric.test_get_value("store1").unwrap();
+
+ let events: Vec<&RecordedEvent> = recorded
+ .iter()
+ .filter(|&e| e.category == "telemetry" && e.name == "event_metric_with_extra")
+ .collect();
+ assert_eq!(events.len(), 2);
+ assert_eq!(events[0].extra, None);
+ assert!(events[1].extra.as_ref().unwrap().get("extra1").unwrap() == "a-valid-value");
+ }
+
+ #[test]
+ #[ignore] // TODO: Enable them back when bug 1673668 lands.
+ fn event_ipc() {
+ use metrics::test_only_ipc::AnEventKeys;
+
+ 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 mut map = HashMap::new();
+ map.insert(AnEventKeys::Extra1, "a-child-value".into());
+ child_metric.record(map);
+ }
+
+ // Record in the parent after the child.
+ let mut map = HashMap::new();
+ map.insert(AnEventKeys::Extra1, "a-valid-value".into());
+ parent_metric.record(map);
+
+ let recorded = parent_metric.test_get_value("store1").unwrap();
+
+ let events: Vec<&RecordedEvent> = recorded
+ .iter()
+ .filter(|&e| e.category == "test_only.ipc" && e.name == "an_event")
+ .collect();
+ assert_eq!(events.len(), 2);
+ assert_eq!(events[0].extra, None);
+
+ assert!(events[1].extra.as_ref().unwrap().get("extra1").unwrap() == "a-valid-value");
+ // TODO: implement replay. See bug 1646165. Then change and add asserts for a-child-value.
+ // Ensure the replay values apply without error, at least.
+ let buf = ipc::take_buf().unwrap();
+ assert!(buf.len() > 0);
+ assert!(ipc::replay_from_buf(&ipc::take_buf().unwrap()).is_ok());
+ }
+}
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..e7e4de61d2
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/labeled.rs
@@ -0,0 +1,333 @@
+// 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::{BooleanMetric, CommonMetricData, CounterMetric, ErrorType, MetricId, StringMetric};
+use crate::ipc::need_ipc;
+
+/// Sealed traits protect against downstream implementations.
+///
+/// We wrap it in a private module that is inaccessible outside of this module.
+mod private {
+ use super::{BooleanMetric, CounterMetric, StringMetric};
+
+ /// 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;
+ }
+
+ // `LabeledMetric<BooleanMetric>` is possible.
+ //
+ // See [Labeled Booleans](https://mozilla.github.io/glean/book/user/metrics/labeled_booleans.html).
+ impl Sealed for BooleanMetric {
+ type GleanMetric = glean::private::BooleanMetric;
+ }
+
+ // `LabeledMetric<StringMetric>` is possible.
+ //
+ // See [Labeled Strings](https://mozilla.github.io/glean/book/user/metrics/labeled_strings.html).
+ impl Sealed for StringMetric {
+ type GleanMetric = glean::private::StringMetric;
+ }
+
+ // `LabeledMetric<CounterMetric>` is possible.
+ //
+ // See [Labeled Counters](https://mozilla.github.io/glean/book/user/metrics/labeled_counters.html).
+ impl Sealed for CounterMetric {
+ type GleanMetric = glean::private::CounterMetric;
+ }
+}
+
+/// Marker trait for metrics that can be nested inside a labeled metric.
+///
+/// This trait is sealed and cannot be implemented for types outside this crate.
+pub trait AllowLabeled: private::Sealed {}
+
+// Implement the trait for everything we marked as allowed.
+impl<T> AllowLabeled for T where T: private::Sealed {}
+
+/// A labeled metric.
+///
+/// Labeled metrics allow to record multiple sub-metrics of the same type under different string labels.
+///
+/// ## Example
+///
+/// The following piece of code will be generated by `glean_parser`:
+///
+/// ```rust,ignore
+/// use glean::metrics::{LabeledMetric, BooleanMetric, CommonMetricData, Lifetime};
+/// use once_cell::sync::Lazy;
+///
+/// mod error {
+/// pub static seen_one: Lazy<LabeledMetric<BooleanMetric>> = Lazy::new(|| LabeledMetric::new(CommonMetricData {
+/// name: "seen_one".into(),
+/// category: "error".into(),
+/// send_in_pings: vec!["ping".into()],
+/// disabled: false,
+/// lifetime: Lifetime::Ping,
+/// ..Default::default()
+/// }, None));
+/// }
+/// ```
+///
+/// It can then be used with:
+///
+/// ```rust,ignore
+/// errro::seen_one.get("upload").set(true);
+/// ```
+//#[derive(Debug)]
+pub struct LabeledMetric<T: AllowLabeled> {
+ // TODO: the `_id` is currently not needed, hence the
+ // prefix, but it will be needed when adding IPC support
+ // to this type.
+ /// The metric ID of the underlying metric.
+ _id: MetricId,
+
+ /// Wrapping the underlying core metric.
+ ///
+ /// We delegate all functionality to this and wrap it up again in our own metric type.
+ core: glean::private::LabeledMetric<T::GleanMetric>,
+}
+
+impl<T> LabeledMetric<T>
+where
+ T: AllowLabeled,
+{
+ /// Create a new labeled metric from the given metric instance and optional list of labels.
+ ///
+ /// See [`get`](#method.get) for information on how static or dynamic labels are handled.
+ pub fn new(
+ id: MetricId,
+ meta: CommonMetricData,
+ labels: Option<Vec<String>>,
+ ) -> LabeledMetric<T> {
+ let core = glean::private::LabeledMetric::new(meta, labels);
+ LabeledMetric { _id: id, core }
+ }
+}
+
+#[inherent(pub)]
+impl<U> glean_core::traits::Labeled<U::GleanMetric> for LabeledMetric<U>
+where
+ U: AllowLabeled + Clone,
+{
+ /// Gets a specific metric for a given label.
+ ///
+ /// If a set of acceptable labels were specified in the `metrics.yaml` file,
+ /// and the given label is not in the set, it will be recorded under the special `OTHER_LABEL` label.
+ ///
+ /// If a set of acceptable labels was not specified in the `metrics.yaml` file,
+ /// only the first 16 unique labels will be used.
+ /// After that, any additional labels will be recorded under the special `OTHER_LABEL` label.
+ ///
+ /// Labels must be `snake_case` and less than 30 characters.
+ /// If an invalid label is used, the metric will be recorded in the special `OTHER_LABEL` label.
+ fn get(&self, label: &str) -> U::GleanMetric {
+ if need_ipc() {
+ panic!("Use of labeled metrics in IPC land not yet implemented!");
+ } else {
+ self.core.get(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.
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ if need_ipc() {
+ panic!("Use of labeled metrics in IPC land not yet implemented!");
+ } else {
+ self.core.test_get_num_recorded_errors(error, ping_name)
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use once_cell::sync::Lazy;
+
+ use super::*;
+ use crate::common_test::*;
+
+ // Smoke test for what should be the generated code.
+ static GLOBAL_METRIC: Lazy<LabeledMetric<BooleanMetric>> = 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<BooleanMetric> = 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<StringMetric> = 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<CounterMetric> = 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<BooleanMetric> = LabeledMetric::new(
+ 0.into(),
+ CommonMetricData {
+ name: "bool".into(),
+ category: "labeled".into(),
+ send_in_pings: store_names,
+ disabled: false,
+ ..Default::default()
+ },
+ None,
+ );
+
+ metric
+ .get("this_string_has_more_than_thirty_characters")
+ .set(true);
+
+ assert_eq!(
+ 1,
+ metric.test_get_num_recorded_errors(ErrorType::InvalidLabel, None)
+ );
+ }
+
+ #[test]
+ fn predefined_labels() {
+ let _lock = lock_test();
+ let store_names: Vec<String> = vec!["store1".into()];
+
+ let metric: LabeledMetric<BooleanMetric> = 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, None)
+ );
+ }
+}
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..4db7f73a2b
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/memory_distribution.rs
@@ -0,0 +1,193 @@
+// 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, DistributionData, MemoryUnit, MetricId};
+
+use glean_core::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(pub)]
+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.
+ fn accumulate(&self, sample: u64) {
+ match self {
+ MemoryDistributionMetric::Parent { inner, .. } => {
+ MemoryDistribution::accumulate(&*inner, 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.
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<DistributionData> {
+ 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.
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: glean::ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ match self {
+ MemoryDistributionMetric::Parent { inner, .. } => {
+ inner.test_get_num_recorded_errors(error, ping_name)
+ }
+ 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);
+
+ // TODO: implement replay. See bug 1646165.
+ // For now, let's ensure there's something in the buffer and replay doesn't error.
+ let buf = ipc::take_buf().unwrap();
+ assert!(buf.len() > 0);
+ assert!(ipc::replay_from_buf(&buf).is_ok());
+ }
+}
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..1e77e4b49e
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/mod.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/.
+
+//! The different metric types supported by the Glean SDK to handle data.
+
+use std::convert::TryFrom;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+use serde::{Deserialize, Serialize};
+
+// Re-export of `glean_core` types we can re-use.
+// That way a user only needs to depend on this crate, not on glean_core (and there can't be a
+// version mismatch).
+pub use glean_core::{
+ metrics::DistributionData, metrics::MemoryUnit, metrics::RecordedEvent, metrics::TimeUnit,
+ CommonMetricData, ErrorType, Lifetime,
+};
+
+mod boolean;
+mod counter;
+mod datetime;
+mod event;
+mod labeled;
+mod memory_distribution;
+mod ping;
+pub(crate) mod string;
+mod string_list;
+mod timespan;
+mod timing_distribution;
+mod uuid;
+
+pub use self::boolean::BooleanMetric;
+pub use self::counter::CounterMetric;
+pub use self::datetime::DatetimeMetric;
+pub use self::event::{EventMetric, EventRecordingError, ExtraKeys, NoExtraKeys};
+pub use self::labeled::LabeledMetric;
+pub use self::memory_distribution::MemoryDistributionMetric;
+pub use self::ping::Ping;
+pub use self::string::StringMetric;
+pub use self::string_list::StringListMetric;
+pub use self::timespan::TimespanMetric;
+pub use self::timing_distribution::TimingDistributionMetric;
+pub use self::uuid::UuidMetric;
+
+/// An instant in time.
+///
+/// Similar to [`std::time::Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html),
+/// but much simpler in that we explicitly expose that it's just an integer.
+///
+/// This is needed, as the current `glean-core` API expects timestamps as integers.
+/// We probably should move this API into `glean-core` directly.
+/// See [Bug 1619253](https://bugzilla.mozilla.org/show_bug.cgi?id=1619253).
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct Instant(u64);
+
+impl Instant {
+ /// Returns an instant corresponding to "now".
+ fn now() -> Instant {
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("SystemTime before UNIX epoch!");
+ let now = now.as_nanos();
+
+ match u64::try_from(now) {
+ Ok(now) => Instant(now),
+ Err(_) => {
+ // Greetings to 2554 from 2020!
+ panic!("timestamp exceeds value range")
+ }
+ }
+ }
+}
+
+/// Uniquely identifies a single metric within its metric type.
+#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize, Serialize)]
+#[repr(transparent)]
+pub struct MetricId(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/ping.rs b/toolkit/components/glean/api/src/private/ping.rs
new file mode 100644
index 0000000000..2b11f1ad5b
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/ping.rs
@@ -0,0 +1,89 @@
+// 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, Debug)]
+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,
+ reason_codes: Vec<String>,
+ ) -> Self {
+ if need_ipc() {
+ Ping::Child
+ } else {
+ Ping::Parent(glean::private::PingType::new(
+ name,
+ include_client_id,
+ send_if_empty,
+ reason_codes,
+ ))
+ }
+ }
+}
+
+#[inherent(pub)]
+impl glean_core::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.
+ fn submit(&self, reason: Option<&str>) {
+ match self {
+ Ping::Parent(p) => {
+ glean_core::traits::Ping::submit(p, reason);
+ }
+ Ping::Child => {
+ log::error!(
+ "Unable to submit ping {:?} in non-main process. Ignoring.",
+ self
+ );
+ // TODO: Record an error.
+ }
+ };
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use once_cell::sync::Lazy;
+
+ use super::*;
+ use crate::common_test::*;
+
+ // Smoke test for what should be the generated code.
+ static PROTOTYPE_PING: Lazy<Ping> = Lazy::new(|| Ping::new("prototype", false, true, vec![]));
+
+ #[test]
+ fn smoke_test_custom_ping() {
+ let _lock = lock_test();
+
+ // We can only check that nothing explodes.
+ // More comprehensive tests are blocked on bug 1673660.
+ PROTOTYPE_PING.submit(None);
+ }
+}
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..9beb11f3f1
--- /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 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(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(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(pub)]
+impl glean_core::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.
+ fn set<S: Into<std::string::String>>(&self, value: S) {
+ match self {
+ StringMetric::Parent(p) => {
+ glean_core::traits::String::set(&*p, value);
+ }
+ StringMetric::Child(_) => {
+ log::error!("Unable to set string metric in non-main process. Ignoring.");
+ // 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`.
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<std::string::String> {
+ match self {
+ StringMetric::Parent(p) => p.test_get_value(ping_name),
+ StringMetric::Child(_) => {
+ panic!("Cannot get test value for string metric in non-parent 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.
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: glean::ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ match self {
+ StringMetric::Parent(p) => p.test_get_num_recorded_errors(error, ping_name),
+ StringMetric::Child(_) => panic!(
+ "Cannot get the number of recorded errors for string metric in non-parent 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..a583b1fced
--- /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_core::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(pub)]
+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).
+ fn add<S: Into<String>>(&self, value: S) {
+ match self {
+ StringListMetric::Parent { inner, .. } => {
+ StringList::add(&*inner, value);
+ }
+ 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 mut v = vec![];
+ v.push(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.
+ fn set(&self, value: Vec<String>) {
+ match self {
+ StringListMetric::Parent { inner, .. } => {
+ StringList::set(&*inner, value);
+ }
+ StringListMetric::Child(c) => {
+ log::error!(
+ "Unable to set string list metric {:?} in non-main process. Ignoring.",
+ c.0
+ );
+ // 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.
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<Vec<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.
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: glean::ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ match self {
+ StringListMetric::Parent { inner, .. } => {
+ inner.test_get_num_recorded_errors(error, ping_name)
+ }
+ 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/timespan.rs b/toolkit/components/glean/api/src/private/timespan.rs
new file mode 100644
index 0000000000..af740d5a41
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/timespan.rs
@@ -0,0 +1,134 @@
+// 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 glean_core::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),
+ 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))
+ }
+ }
+}
+
+#[inherent(pub)]
+impl Timespan for TimespanMetric {
+ fn start(&self) {
+ match self {
+ TimespanMetric::Parent(p) => Timespan::start(p),
+ TimespanMetric::Child => {
+ log::error!("Unable to start timespan metric in non-main process. Ignoring.");
+ // TODO: Record an error.
+ }
+ }
+ }
+
+ fn stop(&self) {
+ match self {
+ TimespanMetric::Parent(p) => Timespan::stop(p),
+ TimespanMetric::Child => {
+ log::error!("Unable to stop timespan metric in non-main process. Ignoring.");
+ // TODO: Record an error.
+ }
+ }
+ }
+
+ fn cancel(&self) {
+ match self {
+ TimespanMetric::Parent(p) => Timespan::cancel(p),
+ TimespanMetric::Child => {
+ log::error!("Unable to cancel timespan metric in non-main process. Ignoring.");
+ // TODO: Record an error.
+ }
+ }
+ }
+
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, ping_name: S) -> Option<u64> {
+ match self {
+ TimespanMetric::Parent(p) => p.test_get_value(ping_name),
+ TimespanMetric::Child => {
+ panic!("Cannot get test value for in non-parent process!");
+ }
+ }
+ }
+
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: glean::ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ match self {
+ TimespanMetric::Parent(p) => p.test_get_num_recorded_errors(error, ping_name),
+ TimespanMetric::Child => {
+ panic!("Cannot get the number of recorded errors for timespan metric in non-parent 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..bfab77cc4e
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/timing_distribution.rs
@@ -0,0 +1,273 @@
+// 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::Instant;
+
+use super::{CommonMetricData, MetricId, TimeUnit};
+use glean::{DistributionData, ErrorType};
+use glean_core::metrics::TimerId;
+
+use crate::ipc::{need_ipc, with_ipc_payload};
+use glean_core::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.
+ ///
+ /// **TEST-ONLY** - Do not use unless gated with `#[cfg(test)]`.
+ 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")
+ }
+ }
+ }
+}
+
+#[inherent(pub)]
+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.
+ fn start(&self) -> TimerId {
+ match self {
+ TimingDistributionMetric::Parent { inner, .. } => inner.start(),
+ 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.
+ }
+ id
+ }
+ }
+ }
+
+ /// 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.
+ fn stop_and_accumulate(&self, id: TimerId) {
+ match self {
+ TimingDistributionMetric::Parent { inner, .. } => {
+ inner.stop_and_accumulate(id);
+ }
+ TimingDistributionMetric::Child(c) => {
+ let mut map = c
+ .instants
+ .write()
+ .expect("Write lock must've been poisoned.");
+ if let Some(start) = map.remove(&id) {
+ let sample = start.elapsed().as_nanos();
+ 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.
+ fn cancel(&self, id: TimerId) {
+ match self {
+ TimingDistributionMetric::Parent { inner, .. } => {
+ inner.cancel(id);
+ }
+ TimingDistributionMetric::Child(c) => {
+ let mut map = c
+ .instants
+ .write()
+ .expect("Write lock must've been poisoned.");
+ if map.remove(&id).is_none() {
+ // TODO: report an error (cancelled a non-started id).
+ }
+ }
+ }
+ }
+
+ /// **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`.
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(
+ &self,
+ ping_name: S,
+ ) -> Option<DistributionData> {
+ 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.
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ match self {
+ TimingDistributionMetric::Parent { inner, .. } => {
+ inner.test_get_num_recorded_errors(error, ping_name)
+ }
+ 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);
+ }
+
+ // TODO: implement replay. See bug 1646165.
+ // For now let's ensure there's something in the buffer and replay doesn't error.
+ let buf = ipc::take_buf().unwrap();
+ assert!(buf.len() > 0);
+ assert!(ipc::replay_from_buf(&buf).is_ok());
+ }
+}
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..fcffc640fc
--- /dev/null
+++ b/toolkit/components/glean/api/src/private/uuid.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/.
+
+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(pub)]
+impl glean_core::traits::Uuid for UuidMetric {
+ /// Set to the specified value.
+ ///
+ /// ## Arguments
+ ///
+ /// * `value` - The UUID to set the metric to.
+ fn set(&self, value: Uuid) {
+ match self {
+ UuidMetric::Parent(p) => {
+ glean_core::traits::Uuid::set(&*p, value);
+ }
+ UuidMetric::Child(_c) => {
+ log::error!("Unable to set the uuid metric in non-main process. Ignoring.");
+ // 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-parent process.
+ fn generate_and_set(&self) -> Uuid {
+ match self {
+ UuidMetric::Parent(p) => glean_core::traits::Uuid::generate_and_set(&*p),
+ UuidMetric::Child(_c) => {
+ log::error!("Unable to set the uuid metric in non-main process. Ignoring.");
+ // 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.
+ fn test_get_value<'a, S: Into<Option<&'a str>>>(&self, storage_name: S) -> Option<Uuid> {
+ match self {
+ UuidMetric::Parent(p) => p.test_get_value(storage_name),
+ UuidMetric::Child(_c) => panic!("Cannot get test value for in non-parent 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.
+ fn test_get_num_recorded_errors<'a, S: Into<Option<&'a str>>>(
+ &self,
+ error: glean::ErrorType,
+ ping_name: S,
+ ) -> i32 {
+ match self {
+ UuidMetric::Parent(p) => p.test_get_num_recorded_errors(error, ping_name),
+ UuidMetric::Child(_c) => {
+ panic!("Cannot get test value for UuidMetric in non-parent 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"
+ );
+ }
+}
diff --git a/toolkit/components/glean/bindings/Category.cpp b/toolkit/components/glean/bindings/Category.cpp
new file mode 100644
index 0000000000..707482eaec
--- /dev/null
+++ b/toolkit/components/glean/bindings/Category.cpp
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/GleanBinding.h"
+#include "mozilla/glean/bindings/Glean.h"
+#include "mozilla/glean/bindings/Category.h"
+#include "mozilla/glean/bindings/GleanJSMetricsLookup.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(Category)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Category)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Category)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Category)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* Category::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanCategory_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<nsISupports> Category::NamedGetter(const nsAString& aName,
+ bool& aFound) {
+ aFound = false;
+
+ nsCString metricName;
+ metricName.AppendASCII(GetCategoryName(mId), mLength);
+ metricName.AppendLiteral(".");
+ AppendUTF16toUTF8(aName, metricName);
+
+ Maybe<uint32_t> metricIdx = MetricByNameLookup(metricName);
+
+ if (metricIdx.isNothing()) {
+ aFound = false;
+ return nullptr;
+ }
+
+ aFound = true;
+ return NewMetricFromId(metricIdx.value());
+}
+
+bool Category::NameIsEnumerable(const nsAString& aName) { return false; }
+
+void Category::GetSupportedNames(nsTArray<nsString>& aNames) {
+ const char* category = GetCategoryName(mId);
+
+ for (metric_entry_t entry : sMetricByNameLookupEntries) {
+ const char* identifier = GetMetricIdentifier(entry);
+
+ // We're iterating all metrics,
+ // so we need to check for the ones in the right category.
+ //
+ // We need to ensure that we found _only_ the exact category by checking it
+ // is followed by a dot.
+ // We need to check the category first to ensure the string is at least that
+ // long, so the check at `mLength` is valid.
+ if (strncmp(category, identifier, mLength) == 0 &&
+ identifier[mLength] == '.') {
+ const char* metricName = &identifier[mLength + 1];
+ aNames.AppendElement()->AssignASCII(metricName);
+ }
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/Category.h b/toolkit/components/glean/bindings/Category.h
new file mode 100644
index 0000000000..b4a6af01b1
--- /dev/null
+++ b/toolkit/components/glean/bindings/Category.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_Category_h
+#define mozilla_glean_Category_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::glean {
+
+class Category final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Category)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() { return nullptr; }
+
+ Category(uint32_t id, uint32_t length) : mId(id), mLength(length) {}
+
+ already_AddRefed<nsISupports> NamedGetter(const nsAString& aName,
+ bool& aFound);
+ bool NameIsEnumerable(const nsAString& aName);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ private:
+ uint32_t mId;
+ uint32_t mLength;
+
+ protected:
+ virtual ~Category() = default;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_Category_h */
diff --git a/toolkit/components/glean/bindings/Glean.cpp b/toolkit/components/glean/bindings/Glean.cpp
new file mode 100644
index 0000000000..3c6e023be7
--- /dev/null
+++ b/toolkit/components/glean/bindings/Glean.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/GleanBinding.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/glean/bindings/Glean.h"
+#include "mozilla/glean/bindings/Category.h"
+#include "mozilla/glean/bindings/GleanJSMetricsLookup.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(Glean)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Glean)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Glean)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Glean)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* Glean::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanImpl_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+bool Glean::DefineGlean(JSContext* aCx, JS::Handle<JSObject*> aGlobal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(JS::GetClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
+ "Passed object is not a global object!");
+
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> glean(aCx);
+ js::AssertSameCompartment(aCx, aGlobal);
+
+ auto impl = MakeRefPtr<Glean>();
+ if (!dom::GetOrCreateDOMReflector(aCx, impl.get(), &glean)) {
+ return false;
+ }
+
+ return JS_DefineProperty(aCx, aGlobal, "Glean", glean, JSPROP_ENUMERATE);
+}
+
+already_AddRefed<Category> Glean::NamedGetter(const nsAString& aName,
+ bool& aFound) {
+ Maybe<uint32_t> categoryIdx =
+ CategoryByNameLookup(NS_ConvertUTF16toUTF8(aName));
+ if (categoryIdx.isNothing()) {
+ aFound = false;
+ return nullptr;
+ }
+
+ aFound = true;
+ uint32_t length = strlen(&gCategoryStringTable[categoryIdx.value()]);
+ return MakeAndAddRef<Category>(categoryIdx.value(), length);
+}
+
+bool Glean::NameIsEnumerable(const nsAString& aName) { return false; }
+
+void Glean::GetSupportedNames(nsTArray<nsString>& aNames) {
+ for (category_entry_t entry : sCategoryByNameLookupEntries) {
+ const char* categoryName = GetCategoryName(entry);
+ aNames.AppendElement()->AssignASCII(categoryName);
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/Glean.h b/toolkit/components/glean/bindings/Glean.h
new file mode 100644
index 0000000000..dfccc2c3d2
--- /dev/null
+++ b/toolkit/components/glean/bindings/Glean.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_Glean_h
+#define mozilla_glean_Glean_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/Category.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::glean {
+
+class Glean final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Glean)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() { return nullptr; }
+
+ static bool DefineGlean(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
+ already_AddRefed<Category> NamedGetter(const nsAString& aName, bool& aFound);
+ bool NameIsEnumerable(const nsAString& aName);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ protected:
+ virtual ~Glean() = default;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_Glean */
diff --git a/toolkit/components/glean/bindings/GleanPings.cpp b/toolkit/components/glean/bindings/GleanPings.cpp
new file mode 100644
index 0000000000..1af4124111
--- /dev/null
+++ b/toolkit/components/glean/bindings/GleanPings.cpp
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/GleanPings.h"
+
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMJSClass.h"
+#include "mozilla/dom/GleanPingsBinding.h"
+#include "mozilla/glean/bindings/GleanJSPingsLookup.h"
+#include "mozilla/glean/bindings/Ping.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(GleanPings)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(GleanPings)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(GleanPings)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GleanPings)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* GleanPings::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::GleanPingsImpl_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+bool GleanPings::DefineGleanPings(JSContext* aCx,
+ JS::Handle<JSObject*> aGlobal) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(JS::GetClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
+ "Passed object is not a global object!");
+
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> gleanPings(aCx);
+ js::AssertSameCompartment(aCx, aGlobal);
+
+ auto impl = MakeRefPtr<GleanPings>();
+ if (!dom::GetOrCreateDOMReflector(aCx, impl.get(), &gleanPings)) {
+ return false;
+ }
+
+ return JS_DefineProperty(aCx, aGlobal, "GleanPings", gleanPings,
+ JSPROP_ENUMERATE);
+}
+
+already_AddRefed<GleanPing> GleanPings::NamedGetter(const nsAString& aName,
+ bool& aFound) {
+ Maybe<uint32_t> pingId = PingByNameLookup(NS_ConvertUTF16toUTF8(aName));
+ if (pingId.isNothing()) {
+ aFound = false;
+ return nullptr;
+ }
+
+ aFound = true;
+ return MakeAndAddRef<GleanPing>(pingId.value());
+}
+
+bool GleanPings::NameIsEnumerable(const nsAString& aName) { return false; }
+
+void GleanPings::GetSupportedNames(nsTArray<nsString>& aNames) {
+ for (uint8_t idx : sPingByNameLookupEntries) {
+ const char* pingName = GetPingName(idx);
+ aNames.AppendElement()->AssignASCII(pingName);
+ }
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/GleanPings.h b/toolkit/components/glean/bindings/GleanPings.h
new file mode 100644
index 0000000000..55eb1c4c9b
--- /dev/null
+++ b/toolkit/components/glean/bindings/GleanPings.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanPings_h
+#define mozilla_glean_GleanPings_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/glean/bindings/Ping.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::glean {
+
+class GleanPings final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(GleanPings)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsISupports* GetParentObject() { return nullptr; }
+
+ static bool DefineGleanPings(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
+
+ already_AddRefed<GleanPing> NamedGetter(const nsAString& aName, bool& aFound);
+ bool NameIsEnumerable(const nsAString& aName);
+ void GetSupportedNames(nsTArray<nsString>& aNames);
+
+ protected:
+ virtual ~GleanPings() = default;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanPings */
diff --git a/toolkit/components/glean/bindings/MetricTypes.h b/toolkit/components/glean/bindings/MetricTypes.h
new file mode 100644
index 0000000000..a6de2e5f7e
--- /dev/null
+++ b/toolkit/components/glean/bindings/MetricTypes.h
@@ -0,0 +1,19 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Glean_MetricTypes_h
+#define mozilla_Glean_MetricTypes_h
+
+#include "mozilla/glean/bindings/Boolean.h"
+#include "mozilla/glean/bindings/Counter.h"
+#include "mozilla/glean/bindings/Datetime.h"
+#include "mozilla/glean/bindings/Event.h"
+#include "mozilla/glean/bindings/MemoryDistribution.h"
+#include "mozilla/glean/bindings/String.h"
+#include "mozilla/glean/bindings/StringList.h"
+#include "mozilla/glean/bindings/Timespan.h"
+#include "mozilla/glean/bindings/TimingDistribution.h"
+#include "mozilla/glean/bindings/Uuid.h"
+
+#endif // mozilla_Glean_MetricTypes_h
diff --git a/toolkit/components/glean/bindings/private/Boolean.cpp b/toolkit/components/glean/bindings/private/Boolean.cpp
new file mode 100644
index 0000000000..efcf63dad9
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Boolean.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/Boolean.h"
+
+#include "nsString.h"
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+
+namespace mozilla {
+namespace glean {
+
+NS_IMPL_CLASSINFO(GleanBoolean, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanBoolean, nsIGleanBoolean)
+
+NS_IMETHODIMP
+GleanBoolean::Set(bool aValue) {
+ mBoolean.Set(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanBoolean::TestGetValue(const nsACString& aStorageName,
+ JS::MutableHandleValue aResult) {
+ auto result = mBoolean.TestGetValue(aStorageName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ aResult.set(JS::BooleanValue(result.value()));
+ }
+ return NS_OK;
+}
+
+} // namespace glean
+} // namespace mozilla
diff --git a/toolkit/components/glean/bindings/private/Boolean.h b/toolkit/components/glean/bindings/private/Boolean.h
new file mode 100644
index 0000000000..a26bc38699
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Boolean.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanBoolean_h
+#define mozilla_glean_GleanBoolean_h
+
+#include "nsIGleanMetrics.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla {
+namespace glean {
+
+namespace impl {
+
+class BooleanMetric {
+ public:
+ constexpr explicit BooleanMetric(uint32_t id) : mId(id) {}
+
+ /**
+ * Set to the specified boolean value.
+ *
+ * @param value the value to set.
+ */
+ void Set(bool value) const { fog_boolean_set(mId, int(value)); }
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a boolean.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric.
+ */
+ Maybe<bool> TestGetValue(const nsACString& aPingName = nsCString()) const {
+ if (!fog_boolean_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+ return Some(fog_boolean_test_get_value(mId, &aPingName));
+ }
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanBoolean final : public nsIGleanBoolean {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANBOOLEAN
+
+ explicit GleanBoolean(uint32_t id) : mBoolean(id){};
+
+ private:
+ virtual ~GleanBoolean() = default;
+
+ const impl::BooleanMetric mBoolean;
+};
+
+} // namespace glean
+} // namespace mozilla
+
+#endif /* mozilla_glean_GleanBoolean.h */
diff --git a/toolkit/components/glean/bindings/private/Common.cpp b/toolkit/components/glean/bindings/private/Common.cpp
new file mode 100644
index 0000000000..847cfe295b
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Common.cpp
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "Common.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIConsoleService.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla::glean {
+
+// This is copied from TelemetryCommons.cpp (and modified because consoleservice
+// handles threading), but that one is not exported.
+// There's _at least_ a third instance of `LogToBrowserConsole`,
+// but that one is slightly different.
+void LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg) {
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService("@mozilla.org/consoleservice;1"));
+ if (!console) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->Init(aMsg, u""_ns, u""_ns, 0, 0, aLogLevel, "chrome javascript",
+ false /* from private window */, true /* from chrome context */);
+ console->LogMessage(error);
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Common.h b/toolkit/components/glean/bindings/private/Common.h
new file mode 100644
index 0000000000..88aae69dc2
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Common.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_Common_h
+#define mozilla_glean_Common_h
+
+#include "jsapi.h"
+#include "nsIScriptError.h"
+
+namespace mozilla::glean {
+
+/**
+ * Dumps a log message to the Browser Console using the provided level.
+ *
+ * @param aLogLevel The level to use when displaying the message in the browser
+ * console (e.g. nsIScriptError::warningFlag, ...).
+ * @param aMsg The text message to print to the console.
+ */
+void LogToBrowserConsole(uint32_t aLogLevel, const nsAString& aMsg);
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_Common_h */
diff --git a/toolkit/components/glean/bindings/private/Counter.cpp b/toolkit/components/glean/bindings/private/Counter.cpp
new file mode 100644
index 0000000000..2b8a429dc8
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Counter.cpp
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/Counter.h"
+
+#include "nsString.h"
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanCounter, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanCounter, nsIGleanCounter)
+
+NS_IMETHODIMP
+GleanCounter::Add(uint32_t aAmount) {
+ mCounter.Add(aAmount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanCounter::TestGetValue(const nsACString& aStorageName,
+ JS::MutableHandleValue aResult) {
+ auto result = mCounter.TestGetValue(aStorageName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ aResult.set(JS::Int32Value(result.value()));
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Counter.h b/toolkit/components/glean/bindings/private/Counter.h
new file mode 100644
index 0000000000..bafeb0b467
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Counter.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanCounter_h
+#define mozilla_glean_GleanCounter_h
+
+#include "mozilla/Maybe.h"
+#include "nsIGleanMetrics.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class CounterMetric {
+ public:
+ constexpr explicit CounterMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Increases the counter by `amount`.
+ *
+ * @param aAmount The amount to increase by. Should be positive.
+ */
+ void Add(int32_t aAmount = 1) const { fog_counter_add(mId, aAmount); }
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Maybe<int32_t> TestGetValue(const nsACString& aPingName = nsCString()) const {
+ if (!fog_counter_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+ return Some(fog_counter_test_get_value(mId, &aPingName));
+ }
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanCounter final : public nsIGleanCounter {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANCOUNTER
+
+ explicit GleanCounter(uint32_t id) : mCounter(id){};
+
+ private:
+ virtual ~GleanCounter() = default;
+
+ const impl::CounterMetric mCounter;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanCounter_h */
diff --git a/toolkit/components/glean/bindings/private/Datetime.cpp b/toolkit/components/glean/bindings/private/Datetime.cpp
new file mode 100644
index 0000000000..d01616b4ec
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Datetime.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/Datetime.h"
+
+#include "nsString.h"
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+#include "prtime.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanDatetime, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanDatetime, nsIGleanDatetime)
+
+NS_IMETHODIMP
+GleanDatetime::Set(PRTime aValue, uint8_t aOptionalArgc) {
+ if (aOptionalArgc == 0) {
+ mDatetime.Set();
+ } else {
+ PRExplodedTime exploded;
+ PR_ExplodeTime(aValue, PR_LocalTimeParameters, &exploded);
+ mDatetime.Set(&exploded);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanDatetime::TestGetValue(const nsACString& aStorageName, JSContext* aCx,
+ JS::MutableHandleValue aResult) {
+ auto result = mDatetime.TestGetValue(aStorageName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ const NS_ConvertUTF8toUTF16 str(result.value());
+ aResult.set(
+ JS::StringValue(JS_NewUCStringCopyN(aCx, str.Data(), str.Length())));
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Datetime.h b/toolkit/components/glean/bindings/private/Datetime.h
new file mode 100644
index 0000000000..748766f3be
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Datetime.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanDatetime_h
+#define mozilla_glean_GleanDatetime_h
+
+#include "mozilla/Maybe.h"
+#include "nsIGleanMetrics.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsString.h"
+#include "prtime.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class DatetimeMetric {
+ public:
+ constexpr explicit DatetimeMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Set the datetime to the provided value, or the local now.
+ *
+ * @param amount The date value to set.
+ */
+ void Set(const PRExplodedTime* aValue = nullptr) const {
+ PRExplodedTime exploded;
+ if (!aValue) {
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ } else {
+ exploded = *aValue;
+ }
+
+ int32_t offset =
+ exploded.tm_params.tp_gmt_offset + exploded.tm_params.tp_dst_offset;
+ fog_datetime_set(mId, exploded.tm_year, exploded.tm_month + 1,
+ exploded.tm_mday, exploded.tm_hour, exploded.tm_min,
+ exploded.tm_sec, exploded.tm_usec * 1000, offset);
+ }
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Maybe<nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const {
+ if (!fog_datetime_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+ nsCString ret;
+ fog_datetime_test_get_value(mId, &aPingName, &ret);
+ return Some(ret);
+ }
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanDatetime final : public nsIGleanDatetime {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANDATETIME
+
+ explicit GleanDatetime(uint32_t aId) : mDatetime(aId){};
+
+ private:
+ virtual ~GleanDatetime() = default;
+
+ const impl::DatetimeMetric mDatetime;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanDatetime_h */
diff --git a/toolkit/components/glean/bindings/private/DistributionData.h b/toolkit/components/glean/bindings/private/DistributionData.h
new file mode 100644
index 0000000000..2b75c323c1
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/DistributionData.h
@@ -0,0 +1,20 @@
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_DistributionData_h
+#define mozilla_glean_DistributionData_h
+
+#include "nsDataHashtable.h"
+
+namespace mozilla::glean {
+
+struct DistributionData final {
+ uint64_t sum;
+ nsDataHashtable<nsUint64HashKey, uint64_t> values;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_DistributionData_h */
diff --git a/toolkit/components/glean/bindings/private/Event.cpp b/toolkit/components/glean/bindings/private/Event.cpp
new file mode 100644
index 0000000000..7bbe7a865b
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Event.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/Event.h"
+
+#include "Common.h"
+#include "nsString.h"
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+#include "jsapi.h"
+#include "nsIScriptError.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanEvent, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanEvent, nsIGleanEvent)
+
+NS_IMETHODIMP
+GleanEvent::Record(JS::HandleValue aExtra, JSContext* aCx) {
+ if (aExtra.isNullOrUndefined()) {
+ mEvent.Record();
+ return NS_OK;
+ }
+
+ if (!aExtra.isObject()) {
+ LogToBrowserConsole(nsIScriptError::warningFlag,
+ u"Extras need to be an object"_ns);
+ return NS_OK;
+ }
+
+ nsTArray<nsCString> extraKeys;
+ nsTArray<nsCString> extraValues;
+
+ JS::RootedObject obj(aCx, &aExtra.toObject());
+ JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
+ if (!JS_Enumerate(aCx, obj, &ids)) {
+ LogToBrowserConsole(nsIScriptError::warningFlag,
+ u"Failed to enumerate object."_ns);
+ return NS_OK;
+ }
+
+ for (size_t i = 0, n = ids.length(); i < n; i++) {
+ nsAutoJSCString jsKey;
+ if (!jsKey.init(aCx, ids[i])) {
+ LogToBrowserConsole(
+ nsIScriptError::warningFlag,
+ u"Extra dictionary should only contain string keys."_ns);
+ return NS_OK;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS_GetPropertyById(aCx, obj, ids[i], &value)) {
+ LogToBrowserConsole(nsIScriptError::warningFlag,
+ u"Failed to get extra property."_ns);
+ return NS_OK;
+ }
+
+ nsAutoJSCString jsValue;
+ if (!value.isString() || !jsValue.init(aCx, value)) {
+ LogToBrowserConsole(nsIScriptError::warningFlag,
+ u"Extra properties should have string values."_ns);
+ return NS_OK;
+ }
+
+ extraKeys.AppendElement(jsKey);
+ extraValues.AppendElement(jsValue);
+ }
+
+ // Calling the implementation directly, because we have a `string->string`
+ // map, not a `T->string` map the C++ API expects.
+ impl::fog_event_record_str(mEvent.mId, &extraKeys, &extraValues);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanEvent::TestGetValue(const nsACString& aStorageName, JSContext* aCx,
+ JS::MutableHandleValue aResult) {
+ auto result = mEvent.TestGetValue(aStorageName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ return NS_OK;
+ }
+
+ // TODO(bug 1678567): Implement this.
+ return NS_ERROR_FAILURE;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Event.h b/toolkit/components/glean/bindings/private/Event.h
new file mode 100644
index 0000000000..d452ffe316
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Event.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanEvent_h
+#define mozilla_glean_GleanEvent_h
+
+#include "nsIGleanMetrics.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/Tuple.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla::glean {
+
+// forward declaration
+class GleanEvent;
+
+namespace impl {
+
+/**
+ * Represents the recorded data for a single event
+ */
+struct RecordedEvent {
+ public:
+ uint64_t mTimestamp;
+ nsCString mCategory;
+ nsCString mName;
+
+ nsTArray<Tuple<nsCString, nsCString>> mExtra;
+};
+
+template <class T>
+class EventMetric {
+ friend class mozilla::glean::GleanEvent;
+
+ public:
+ constexpr explicit EventMetric(uint32_t id) : mId(id) {}
+
+ /**
+ * Record an event.
+ *
+ * @param aExtras The list of (extra key, value) pairs. Allowed extra keys are
+ * defined in the metric definition.
+ * If the wrong keys are used or values are too large
+ * an error is report and no event is recorded.
+ */
+ void Record(const Span<const Tuple<T, nsCString>>& aExtras = {}) const {
+ static_assert(sizeof(T) <= sizeof(int32_t),
+ "Extra keys need to fit into 32 bits");
+
+ nsTArray<int32_t> extraKeys;
+ nsTArray<nsCString> extraValues;
+ for (auto& entry : aExtras) {
+ extraKeys.AppendElement(static_cast<int32_t>(mozilla::Get<0>(entry)));
+ extraValues.AppendElement(mozilla::Get<1>(entry));
+ }
+
+ fog_event_record(mId, &extraKeys, &extraValues);
+ }
+
+ /**
+ * **Test-only API**
+ *
+ * Get a list of currently stored events for this event metric.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Maybe<nsTArray<RecordedEvent>> TestGetValue(
+ const nsACString& aPingName = nsCString()) const {
+ if (!fog_event_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+
+ // TODO(bug 1678567): Implement this.
+ nsTArray<RecordedEvent> empty;
+ return Some(std::move(empty));
+ }
+
+ private:
+ const uint32_t mId;
+};
+
+} // namespace impl
+
+class GleanEvent final : public nsIGleanEvent {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANEVENT
+
+ explicit GleanEvent(uint32_t id) : mEvent(id){};
+
+ private:
+ virtual ~GleanEvent() = default;
+
+ const impl::EventMetric<uint32_t> mEvent;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanEvent.h */
diff --git a/toolkit/components/glean/bindings/private/MemoryDistribution.cpp b/toolkit/components/glean/bindings/private/MemoryDistribution.cpp
new file mode 100644
index 0000000000..82043d8ddb
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/MemoryDistribution.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/MemoryDistribution.h"
+
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+#include "nsJSUtils.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanMemoryDistribution, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanMemoryDistribution, nsIGleanMemoryDistribution)
+
+NS_IMETHODIMP
+GleanMemoryDistribution::Accumulate(uint64_t aSample) {
+ mMemoryDist.Accumulate(aSample);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanMemoryDistribution::TestGetValue(const nsACString& aPingName,
+ JSContext* aCx,
+ JS::MutableHandleValue aResult) {
+ auto result = mMemoryDist.TestGetValue(aPingName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ // Build return value of the form: { sum: #, values: {bucket1: count1, ...}
+ JS::RootedObject root(aCx, JS_NewPlainObject(aCx));
+ if (!root) {
+ return NS_ERROR_FAILURE;
+ }
+ uint64_t sum = result.ref().sum;
+ if (!JS_DefineProperty(aCx, root, "sum", static_cast<double>(sum),
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::RootedObject valuesObj(aCx, JS_NewPlainObject(aCx));
+ if (!valuesObj ||
+ !JS_DefineProperty(aCx, root, "values", valuesObj, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ auto& data = result.ref().values;
+ for (auto iter = data.ConstIter(); !iter.Done(); iter.Next()) {
+ const uint64_t bucket = iter.Key();
+ const uint64_t count = iter.UserData();
+ if (!JS_DefineProperty(aCx, valuesObj,
+ nsPrintfCString("%" PRIu64, bucket).get(),
+ static_cast<double>(count), JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ aResult.setObject(*root);
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/MemoryDistribution.h b/toolkit/components/glean/bindings/private/MemoryDistribution.h
new file mode 100644
index 0000000000..155ffa115c
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/MemoryDistribution.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanMemoryDistribution_h
+#define mozilla_glean_GleanMemoryDistribution_h
+
+#include "mozilla/glean/bindings/DistributionData.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/Maybe.h"
+#include "nsIGleanMetrics.h"
+#include "nsTArray.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class MemoryDistributionMetric {
+ public:
+ constexpr explicit MemoryDistributionMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Accumulates the provided sample in the metric.
+ *
+ * @param aSample The sample to be recorded by the metric. The sample is
+ * assumed to be in the confgured memory unit of the metric.
+ *
+ * Notes: Values bigger than 1 Terabyte (2^40 bytes) are truncated and an
+ * InvalidValue error is recorded.
+ */
+ void Accumulate(uint64_t aSample) const {
+ fog_memory_distribution_accumulate(mId, aSample);
+ }
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a DistributionData.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Maybe<DistributionData> TestGetValue(
+ const nsACString& aPingName = nsCString()) const {
+ if (!fog_memory_distribution_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+ nsTArray<uint64_t> buckets;
+ nsTArray<uint64_t> counts;
+ DistributionData ret;
+ fog_memory_distribution_test_get_value(mId, &aPingName, &ret.sum, &buckets,
+ &counts);
+ for (size_t i = 0; i < buckets.Length(); ++i) {
+ ret.values.Put(buckets[i], counts[i]);
+ }
+ return Some(std::move(ret));
+ }
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanMemoryDistribution final : public nsIGleanMemoryDistribution {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANMEMORYDISTRIBUTION
+
+ explicit GleanMemoryDistribution(uint64_t aId) : mMemoryDist(aId){};
+
+ private:
+ virtual ~GleanMemoryDistribution() = default;
+
+ const impl::MemoryDistributionMetric mMemoryDist;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanMemoryDistribution_h */
diff --git a/toolkit/components/glean/bindings/private/Ping.cpp b/toolkit/components/glean/bindings/private/Ping.cpp
new file mode 100644
index 0000000000..61e0434b78
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Ping.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/Ping.h"
+
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanPing, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanPing, nsIGleanPing)
+
+NS_IMETHODIMP
+GleanPing::Submit(const nsACString& aReason) {
+ mPing.Submit(aReason);
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Ping.h b/toolkit/components/glean/bindings/private/Ping.h
new file mode 100644
index 0000000000..14c2dd8646
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Ping.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_Ping_h
+#define mozilla_glean_Ping_h
+
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/Maybe.h"
+#include "nsIGleanMetrics.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class Ping {
+ public:
+ constexpr explicit Ping(uint32_t aId) : mId(aId) {}
+
+ /**
+ * Collect and submit the ping for eventual upload.
+ *
+ * This will collect all stored data to be included in the ping.
+ * Data with lifetime `ping` will then be reset.
+ *
+ * If the ping is configured with `send_if_empty = false`
+ * and the ping currently contains no content,
+ * it will not be queued for upload.
+ * If the ping is configured with `send_if_empty = true`
+ * it will be queued for upload even if empty.
+ *
+ * Pings always contain the `ping_info` and `client_info` sections.
+ * See [ping
+ * sections](https://mozilla.github.io/glean/book/user/pings/index.html#ping-sections)
+ * for details.
+ *
+ * @param aReason - Optional. The reason the ping is being submitted.
+ * Must match one of the configured `reason_codes`.
+ */
+ void Submit(const nsACString& aReason = nsCString()) const {
+ fog_submit_ping_by_id(mId, &aReason);
+ }
+
+ private:
+ const uint32_t mId;
+};
+
+} // namespace impl
+
+class GleanPing final : public nsIGleanPing {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANPING
+
+ explicit GleanPing(uint32_t aId) : mPing(aId) {}
+
+ private:
+ virtual ~GleanPing() = default;
+
+ const impl::Ping mPing;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_Ping_h */
diff --git a/toolkit/components/glean/bindings/private/String.cpp b/toolkit/components/glean/bindings/private/String.cpp
new file mode 100644
index 0000000000..6c5d9909af
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/String.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/String.h"
+
+#include "nsString.h"
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanString, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanString, nsIGleanString)
+
+NS_IMETHODIMP
+GleanString::Set(const nsACString& aValue) {
+ mString.Set(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanString::TestGetValue(const nsACString& aStorageName, JSContext* aCx,
+ JS::MutableHandleValue aResult) {
+ auto result = mString.TestGetValue(aStorageName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ const NS_ConvertUTF8toUTF16 str(result.value());
+ aResult.set(
+ JS::StringValue(JS_NewUCStringCopyN(aCx, str.Data(), str.Length())));
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/String.h b/toolkit/components/glean/bindings/private/String.h
new file mode 100644
index 0000000000..5aff67171a
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/String.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanString_h
+#define mozilla_glean_GleanString_h
+
+#include "mozilla/Maybe.h"
+#include "nsIGleanMetrics.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class StringMetric {
+ public:
+ constexpr explicit StringMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Set to the specified value.
+ *
+ * Truncates the value if it is longer than 100 bytes and logs an error.
+ * See https://mozilla.github.io/glean/book/user/metrics/string.html#limits.
+ *
+ * @param aValue The string to set the metric to.
+ */
+ void Set(const nsACString& aValue) const { fog_string_set(mId, &aValue); }
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a string.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Maybe<nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const {
+ if (!fog_string_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+ nsCString ret;
+ fog_string_test_get_value(mId, &aPingName, &ret);
+ return Some(ret);
+ }
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanString final : public nsIGleanString {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANSTRING
+
+ explicit GleanString(uint32_t aId) : mString(aId){};
+
+ private:
+ virtual ~GleanString() = default;
+
+ const impl::StringMetric mString;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanString_h */
diff --git a/toolkit/components/glean/bindings/private/StringList.cpp b/toolkit/components/glean/bindings/private/StringList.cpp
new file mode 100644
index 0000000000..e2faeec0ba
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/StringList.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/StringList.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsIClassInfoImpl.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanStringList, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanStringList, nsIGleanStringList)
+
+NS_IMETHODIMP
+GleanStringList::Add(const nsACString& aValue) {
+ mStringList.Add(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanStringList::Set(const nsTArray<nsCString>& aValue) {
+ mStringList.Set(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanStringList::TestGetValue(const nsACString& aStorageName, JSContext* aCx,
+ JS::MutableHandleValue aResult) {
+ auto result = mStringList.TestGetValue(aStorageName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ if (!dom::ToJSValue(aCx, result.ref(), aResult)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/StringList.h b/toolkit/components/glean/bindings/private/StringList.h
new file mode 100644
index 0000000000..c2774e30ac
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/StringList.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanStringList_h
+#define mozilla_glean_GleanStringList_h
+
+#include "mozilla/Maybe.h"
+#include "nsIGleanMetrics.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class StringListMetric {
+ public:
+ constexpr explicit StringListMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Adds a new string to the list.
+ *
+ * Truncates the value if it is longer than 50 bytes and logs an error.
+ *
+ * @param aValue The string to add.
+ */
+ void Add(const nsACString& aValue) const {
+ fog_string_list_add(mId, &aValue);
+ }
+
+ /*
+ * Set to a specific list of strings.
+ *
+ * Truncates any values longer than 50 bytes and logs an error.
+ * Truncates the list if it is over 20 items long.
+ * See
+ * https://mozilla.github.io/glean/book/user/metrics/string_list.html#limits.
+ *
+ * @param aValue The list of strings to set the metric to.
+ */
+ void Set(const nsTArray<nsCString>& aValue) const {
+ fog_string_list_set(mId, &aValue);
+ }
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Maybe<nsTArray<nsCString>> TestGetValue(
+ const nsACString& aPingName = nsCString()) const {
+ if (!fog_string_list_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+ nsTArray<nsCString> ret;
+ fog_string_list_test_get_value(mId, &aPingName, &ret);
+ return Some(std::move(ret));
+ }
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanStringList final : public nsIGleanStringList {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANSTRINGLIST
+
+ explicit GleanStringList(uint32_t aId) : mStringList(aId){};
+
+ private:
+ virtual ~GleanStringList() = default;
+
+ const impl::StringListMetric mStringList;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanStringList_h */
diff --git a/toolkit/components/glean/bindings/private/Timespan.cpp b/toolkit/components/glean/bindings/private/Timespan.cpp
new file mode 100644
index 0000000000..0c2dd5125e
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Timespan.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/Timespan.h"
+
+#include "nsString.h"
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanTimespan, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanTimespan, nsIGleanTimespan)
+
+NS_IMETHODIMP
+GleanTimespan::Start() {
+ mTimespan.Start();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanTimespan::Stop() {
+ mTimespan.Stop();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanTimespan::TestGetValue(const nsACString& aStorageName,
+ JS::MutableHandleValue aResult) {
+ auto result = mTimespan.TestGetValue(aStorageName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ aResult.set(JS::DoubleValue(result.value()));
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Timespan.h b/toolkit/components/glean/bindings/private/Timespan.h
new file mode 100644
index 0000000000..6425c03046
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Timespan.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanTimespan_h
+#define mozilla_glean_GleanTimespan_h
+
+#include "mozilla/Maybe.h"
+#include "nsIGleanMetrics.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class TimespanMetric {
+ public:
+ constexpr explicit TimespanMetric(uint32_t aId) : mId(aId) {}
+
+ /**
+ * Start tracking time for the provided metric.
+ *
+ * This records an error if it’s already tracking time (i.e. start was already
+ * called with no corresponding [stop]): in that case the original
+ * start time will be preserved.
+ */
+ void Start() const { fog_timespan_start(mId); }
+
+ /**
+ * Stop tracking time for the provided metric.
+ *
+ * Sets the metric to the elapsed time, but does not overwrite an already
+ * existing value.
+ * This will record an error if no [start] was called or there is an already
+ * existing value.
+ */
+ void Stop() const { fog_timespan_stop(mId); }
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Maybe<int64_t> TestGetValue(const nsACString& aPingName = nsCString()) const {
+ if (!fog_timespan_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+ return Some(fog_timespan_test_get_value(mId, &aPingName));
+ }
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanTimespan final : public nsIGleanTimespan {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANTIMESPAN
+
+ explicit GleanTimespan(uint32_t aId) : mTimespan(aId){};
+
+ private:
+ virtual ~GleanTimespan() = default;
+
+ const impl::TimespanMetric mTimespan;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanTimespan_h */
diff --git a/toolkit/components/glean/bindings/private/TimingDistribution.cpp b/toolkit/components/glean/bindings/private/TimingDistribution.cpp
new file mode 100644
index 0000000000..129adea79d
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/TimingDistribution.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/TimingDistribution.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "nsIClassInfoImpl.h"
+#include "nsJSUtils.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanTimingDistribution, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanTimingDistribution, nsIGleanTimingDistribution)
+
+NS_IMETHODIMP
+GleanTimingDistribution::Start(JSContext* aCx, JS::MutableHandleValue aResult) {
+ if (!dom::ToJSValue(aCx, mTimingDist.Start(), aResult)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanTimingDistribution::StopAndAccumulate(uint64_t aId) {
+ mTimingDist.StopAndAccumulate(std::move(aId));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanTimingDistribution::Cancel(uint64_t aId) {
+ mTimingDist.Cancel(std::move(aId));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanTimingDistribution::TestGetValue(const nsACString& aPingName,
+ JSContext* aCx,
+ JS::MutableHandleValue aResult) {
+ auto result = mTimingDist.TestGetValue(aPingName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ // Build return value of the form: { sum: #, values: {bucket1: count1, ...}
+ JS::RootedObject root(aCx, JS_NewPlainObject(aCx));
+ if (!root) {
+ return NS_ERROR_FAILURE;
+ }
+ uint64_t sum = result.ref().sum;
+ if (!JS_DefineProperty(aCx, root, "sum", static_cast<double>(sum),
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::RootedObject valuesObj(aCx, JS_NewPlainObject(aCx));
+ if (!valuesObj ||
+ !JS_DefineProperty(aCx, root, "values", valuesObj, JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ auto& data = result.ref().values;
+ for (auto iter = data.ConstIter(); !iter.Done(); iter.Next()) {
+ const uint64_t bucket = iter.Key();
+ const uint64_t count = iter.UserData();
+ if (!JS_DefineProperty(aCx, valuesObj,
+ nsPrintfCString("%" PRIu64, bucket).get(),
+ static_cast<double>(count), JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ aResult.setObject(*root);
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/TimingDistribution.h b/toolkit/components/glean/bindings/private/TimingDistribution.h
new file mode 100644
index 0000000000..6217d0fa42
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/TimingDistribution.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanTimingDistribution_h
+#define mozilla_glean_GleanTimingDistribution_h
+
+#include "mozilla/glean/bindings/DistributionData.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/Maybe.h"
+#include "nsDataHashtable.h"
+#include "nsIGleanMetrics.h"
+#include "nsTArray.h"
+
+namespace mozilla::glean {
+
+typedef uint64_t TimerId;
+
+namespace impl {
+
+class TimingDistributionMetric {
+ public:
+ constexpr explicit TimingDistributionMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Starts tracking time for the provided metric.
+ *
+ * @returns A unique TimerId for the new timer
+ */
+ TimerId Start() const { return fog_timing_distribution_start(mId); }
+
+ /*
+ * 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` was called on this TimerId or
+ * if this TimerId was used to call `Cancel`.
+ *
+ * @param aId The TimerId to associate with this timing. This allows for
+ * concurrent timing of events associated with different ids.
+ */
+ void StopAndAccumulate(TimerId&& aId) const {
+ fog_timing_distribution_stop_and_accumulate(mId, aId);
+ }
+
+ /*
+ * Aborts a previous `Start` call. No error is recorded if no `Start` was
+ * called.
+ *
+ * @param aId The TimerId whose `Start` you wish to abort.
+ */
+ void Cancel(TimerId&& aId) const { fog_timing_distribution_cancel(mId, aId); }
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a DistributionData.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Maybe<DistributionData> TestGetValue(
+ const nsACString& aPingName = nsCString()) const {
+ if (!fog_timing_distribution_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+ nsTArray<uint64_t> buckets;
+ nsTArray<uint64_t> counts;
+ DistributionData ret;
+ fog_timing_distribution_test_get_value(mId, &aPingName, &ret.sum, &buckets,
+ &counts);
+ for (size_t i = 0; i < buckets.Length(); ++i) {
+ ret.values.Put(buckets[i], counts[i]);
+ }
+ return Some(std::move(ret));
+ }
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanTimingDistribution final : public nsIGleanTimingDistribution {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANTIMINGDISTRIBUTION
+
+ explicit GleanTimingDistribution(uint64_t aId) : mTimingDist(aId){};
+
+ private:
+ virtual ~GleanTimingDistribution() = default;
+
+ const impl::TimingDistributionMetric mTimingDist;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanTimingDistribution_h */
diff --git a/toolkit/components/glean/bindings/private/Uuid.cpp b/toolkit/components/glean/bindings/private/Uuid.cpp
new file mode 100644
index 0000000000..d119fcb5ec
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Uuid.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/glean/bindings/Uuid.h"
+
+#include "nsString.h"
+#include "mozilla/Components.h"
+#include "nsIClassInfoImpl.h"
+
+namespace mozilla::glean {
+
+NS_IMPL_CLASSINFO(GleanUuid, nullptr, 0, {0})
+NS_IMPL_ISUPPORTS_CI(GleanUuid, nsIGleanUuid)
+
+NS_IMETHODIMP
+GleanUuid::Set(const nsACString& aValue) {
+ mUuid.Set(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanUuid::GenerateAndSet() {
+ mUuid.GenerateAndSet();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GleanUuid::TestGetValue(const nsACString& aStorageName, JSContext* aCx,
+ JS::MutableHandleValue aResult) {
+ auto result = mUuid.TestGetValue(aStorageName);
+ if (result.isNothing()) {
+ aResult.set(JS::UndefinedValue());
+ } else {
+ const NS_ConvertUTF8toUTF16 str(result.value());
+ aResult.set(
+ JS::StringValue(JS_NewUCStringCopyN(aCx, str.Data(), str.Length())));
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla::glean
diff --git a/toolkit/components/glean/bindings/private/Uuid.h b/toolkit/components/glean/bindings/private/Uuid.h
new file mode 100644
index 0000000000..5c3690e724
--- /dev/null
+++ b/toolkit/components/glean/bindings/private/Uuid.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_GleanUuid_h
+#define mozilla_glean_GleanUuid_h
+
+#include "mozilla/Maybe.h"
+#include "nsIGleanMetrics.h"
+#include "nsString.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+namespace mozilla::glean {
+
+namespace impl {
+
+class UuidMetric {
+ public:
+ constexpr explicit UuidMetric(uint32_t aId) : mId(aId) {}
+
+ /*
+ * Sets to the specified value.
+ *
+ * @param aValue The UUID to set the metric to.
+ */
+ void Set(const nsACString& aValue) const { fog_uuid_set(mId, &aValue); }
+
+ /*
+ * Generate a new random UUID and set the metric to it.
+ */
+ void GenerateAndSet() const { fog_uuid_generate_and_set(mId); }
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a hyphenated string.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ Maybe<nsCString> TestGetValue(
+ const nsACString& aPingName = nsCString()) const {
+ if (!fog_uuid_test_has_value(mId, &aPingName)) {
+ return Nothing();
+ }
+ nsCString ret;
+ fog_uuid_test_get_value(mId, &aPingName, &ret);
+ return Some(ret);
+ }
+
+ private:
+ const uint32_t mId;
+};
+} // namespace impl
+
+class GleanUuid final : public nsIGleanUuid {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGLEANUUID
+
+ explicit GleanUuid(uint32_t aId) : mUuid(aId){};
+
+ private:
+ virtual ~GleanUuid() = default;
+
+ const impl::UuidMetric mUuid;
+};
+
+} // namespace mozilla::glean
+
+#endif /* mozilla_glean_GleanUuid_h */
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/cpp.py b/toolkit/components/glean/build_scripts/glean_parser_ext/cpp.py
new file mode 100644
index 0000000000..64b9ab489a
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/cpp.py
@@ -0,0 +1,112 @@
+# -*- coding: utf-8 -*-
+
+# 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 http://mozilla.org/MPL/2.0/.
+
+"""
+Outputter to generate C++ code for metrics.
+"""
+
+import jinja2
+import json
+
+from util import generate_metric_ids, generate_ping_ids, is_implemented_metric_type
+from glean_parser import util
+
+
+def cpp_datatypes_filter(value):
+ """
+ A Jinja2 filter that renders Rust literals.
+
+ Based on Python's JSONEncoder, but overrides:
+ - lists to array literals {}
+ - strings to "value"
+ """
+
+ class CppEncoder(json.JSONEncoder):
+ def iterencode(self, value):
+ if isinstance(value, list):
+ yield "{"
+ first = True
+ for subvalue in list(value):
+ if not first:
+ yield ", "
+ yield from self.iterencode(subvalue)
+ first = False
+ yield "}"
+ elif isinstance(value, str):
+ yield '"' + value + '"'
+ else:
+ yield from super().iterencode(value)
+
+ return "".join(CppEncoder().iterencode(value))
+
+
+def type_name(obj):
+ """
+ Returns the C++ type to use for a given metric object.
+ """
+
+ generate_enums = getattr(obj, "_generate_enums", []) # Extra Keys? Reasons?
+ if len(generate_enums):
+ for name, suffix in generate_enums:
+ if not len(getattr(obj, name)) and suffix == "Keys":
+ return util.Camelize(obj.type) + "Metric<uint32_t>"
+ else:
+ return "{}Metric<{}>".format(
+ util.Camelize(obj.type), util.Camelize(obj.name) + suffix
+ )
+ return util.Camelize(obj.type) + "Metric"
+
+
+def output_cpp(objs, output_fd, options={}):
+ """
+ Given a tree of objects, output C++ code to the file-like object `output_fd`.
+
+ :param objs: A tree of objects (metrics and pings) as returned from
+ `parser.parse_objects`.
+ :param output_fd: Writeable file to write the output to.
+ :param options: options dictionary.
+ """
+
+ # Monkeypatch a util.snake_case function for the templates to use
+ util.snake_case = lambda value: value.replace(".", "_").replace("-", "_")
+ # Monkeypatch util.get_jinja2_template to find templates nearby
+
+ def get_local_template(template_name, filters=()):
+ env = jinja2.Environment(
+ loader=jinja2.PackageLoader("cpp", "templates"),
+ trim_blocks=True,
+ lstrip_blocks=True,
+ )
+ env.filters["camelize"] = util.camelize
+ env.filters["Camelize"] = util.Camelize
+ for filter_name, filter_func in filters:
+ env.filters[filter_name] = filter_func
+ return env.get_template(template_name)
+
+ util.get_jinja2_template = get_local_template
+ get_metric_id = generate_metric_ids(objs)
+ get_ping_id = generate_ping_ids(objs)
+
+ if len(objs) == 1 and "pings" in objs:
+ template_filename = "cpp_pings.jinja2"
+ else:
+ template_filename = "cpp.jinja2"
+
+ template = util.get_jinja2_template(
+ template_filename,
+ filters=(
+ ("cpp", cpp_datatypes_filter),
+ ("snake_case", util.snake_case),
+ ("type_name", type_name),
+ ("metric_id", get_metric_id),
+ ("ping_id", get_ping_id),
+ ("is_implemented_type", is_implemented_metric_type),
+ ("Camelize", util.Camelize),
+ ),
+ )
+
+ output_fd.write(template.render(all_objs=objs))
+ output_fd.write("\n")
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/js.py b/toolkit/components/glean/build_scripts/glean_parser_ext/js.py
new file mode 100644
index 0000000000..2334bc7a39
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/js.py
@@ -0,0 +1,254 @@
+# -*- coding: utf-8 -*-
+
+# 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 http://mozilla.org/MPL/2.0/.
+
+"""
+Outputter to generate C++ code for the JavaScript API for metrics.
+
+The code for the JavaScript API is a bit special in that we only generate C++ code,
+string tables and mapping functions.
+The rest is handled by the WebIDL and XPIDL implementation
+that uses this code to look up metrics by name.
+"""
+
+import jinja2
+from perfecthash import PerfectHash
+from string_table import StringTable
+
+from util import generate_metric_ids, generate_ping_ids, is_implemented_metric_type
+from glean_parser import util
+
+"""
+We need to store several bits of information in the Perfect Hash Map Entry:
+
+1. An index into the string table to check for string equality with a search key
+ The perfect hash function will give false-positive for non-existent keys,
+ so we need to verify these ourselves.
+2. Type information to instantiate the correct C++ class
+3. The metric's actual ID to lookup the underlying instance.
+
+We have 64 bits to play with, so we dedicate:
+
+1. 32 bit to the string table offset. More than enough for a large string table (~60M metrics).
+2. 5 bit for the type. That allows for 32 metric types. We're not even close to that yet.
+3. 27 bit for the metric ID. That allows for 130 million metrics. Let's not go there.
+
+These values are interpolated into the template as well, so changing them here
+ensures the generated C++ code follows.
+If we ever need more bits for a part (e.g. when we add the 33rd metric type),
+we figure out if either the string table indices or the range of possible IDs can be reduced
+and adjust the constants below.
+"""
+ENTRY_WIDTH = 64
+INDEX_BITS = 32
+ID_BITS = 27
+
+PING_INDEX_BITS = 16
+
+
+def ping_entry(ping_id, ping_string_index):
+ """
+ The 2 pieces of information of a ping encoded into a single 32-bit integer.
+ """
+ assert ping_id < 2 ** (32 - PING_INDEX_BITS)
+ assert ping_string_index < 2 ** PING_INDEX_BITS
+ return ping_id << PING_INDEX_BITS | ping_string_index
+
+
+def create_entry(metric_id, type_id, idx):
+ """
+ The 3 pieces of information of a metric encoded into a single 64-bit integer.
+ """
+ return metric_id << INDEX_BITS | type_id << (INDEX_BITS + ID_BITS) | idx
+
+
+def metric_identifier(category, metric_name):
+ """
+ The metric's unique identifier, including the category and name
+ """
+ return f"{category}.{util.camelize(metric_name)}"
+
+
+def type_name(type):
+ """
+ Returns the C++ type to use for a given metric object.
+ """
+
+ return "Glean" + util.Camelize(type)
+
+
+def output_js(objs, output_fd, options={}):
+ """
+ Given a tree of objects, output code for the JS API to the file-like object `output_fd`.
+
+ :param objs: A tree of objects (metrics and pings) as returned from
+ `parser.parse_objects`.
+ :param output_fd: Writeable file to write the output to.
+ :param options: options dictionary.
+ """
+
+ # Monkeypatch util.get_jinja2_template to find templates nearby
+
+ def get_local_template(template_name, filters=()):
+ env = jinja2.Environment(
+ loader=jinja2.PackageLoader("js", "templates"),
+ trim_blocks=True,
+ lstrip_blocks=True,
+ )
+ env.filters["Camelize"] = util.Camelize
+ for filter_name, filter_func in filters:
+ env.filters[filter_name] = filter_func
+ return env.get_template(template_name)
+
+ util.get_jinja2_template = get_local_template
+
+ if len(objs) == 1 and "pings" in objs:
+ write_pings(objs, output_fd, "js_pings.jinja2")
+ else:
+ write_metrics(objs, output_fd, "js.jinja2")
+
+
+def write_metrics(objs, output_fd, template_filename):
+ """
+ Given a tree of objects `objs`, output metrics-only code for the JS API to the
+ file-like object `output_fd` using template `template_filename`
+ """
+
+ template = util.get_jinja2_template(
+ template_filename,
+ filters=(
+ ("type_name", type_name),
+ ("is_implemented_type", is_implemented_metric_type),
+ ),
+ )
+
+ assert (
+ INDEX_BITS + ID_BITS < ENTRY_WIDTH
+ ), "INDEX_BITS or ID_BITS are larger than allowed"
+
+ get_metric_id = generate_metric_ids(objs)
+ # Mapping from a metric's identifier to the entry (metric ID | type id | index)
+ metric_id_mapping = {}
+ categories = []
+
+ category_string_table = StringTable()
+ metric_string_table = StringTable()
+ # Mapping from a type name to its ID
+ metric_type_ids = {}
+
+ for category_name, objs in objs.items():
+ category_name = util.camelize(category_name)
+ id = category_string_table.stringIndex(category_name)
+ categories.append((category_name, id))
+
+ for metric in objs.values():
+ identifier = metric_identifier(category_name, metric.name)
+ if metric.type in metric_type_ids:
+ type_id = metric_type_ids[metric.type]
+ else:
+ type_id = len(metric_type_ids) + 1
+ metric_type_ids[metric.type] = type_id
+
+ idx = metric_string_table.stringIndex(identifier)
+ metric_id = get_metric_id(metric)
+ entry = create_entry(metric_id, type_id, idx)
+ metric_id_mapping[identifier] = entry
+
+ # Create a lookup table for the metric categories only
+ category_string_table = category_string_table.writeToString("gCategoryStringTable")
+ category_map = [(bytearray(category, "ascii"), id) for (category, id) in categories]
+ name_phf = PerfectHash(category_map, 64)
+ category_by_name_lookup = name_phf.cxx_codegen(
+ name="CategoryByNameLookup",
+ entry_type="category_entry_t",
+ lower_entry=lambda x: str(x[1]),
+ key_type="const nsACString&",
+ key_bytes="aKey.BeginReading()",
+ key_length="aKey.Length()",
+ return_type="static Maybe<uint32_t>",
+ return_entry="return category_result_check(aKey, entry);",
+ )
+
+ # Create a lookup table for metric's identifiers.
+ metric_string_table = metric_string_table.writeToString("gMetricStringTable")
+ metric_map = [
+ (bytearray(metric_name, "ascii"), metric_id)
+ for (metric_name, metric_id) in metric_id_mapping.items()
+ ]
+ metric_phf = PerfectHash(metric_map, 64)
+ metric_by_name_lookup = metric_phf.cxx_codegen(
+ name="MetricByNameLookup",
+ entry_type="metric_entry_t",
+ lower_entry=lambda x: str(x[1]),
+ key_type="const nsACString&",
+ key_bytes="aKey.BeginReading()",
+ key_length="aKey.Length()",
+ return_type="static Maybe<uint32_t>",
+ return_entry="return metric_result_check(aKey, entry);",
+ )
+
+ output_fd.write(
+ template.render(
+ categories=categories,
+ metric_id_mapping=metric_id_mapping,
+ metric_type_ids=metric_type_ids,
+ entry_width=ENTRY_WIDTH,
+ index_bits=INDEX_BITS,
+ id_bits=ID_BITS,
+ category_string_table=category_string_table,
+ category_by_name_lookup=category_by_name_lookup,
+ metric_string_table=metric_string_table,
+ metric_by_name_lookup=metric_by_name_lookup,
+ )
+ )
+ output_fd.write("\n")
+
+
+def write_pings(objs, output_fd, template_filename):
+ """
+ Given a tree of objects `objs`, output pings-only code for the JS API to the
+ file-like object `output_fd` using template `template_filename`
+ """
+
+ template = util.get_jinja2_template(
+ template_filename,
+ filters=(),
+ )
+
+ ping_string_table = StringTable()
+ get_ping_id = generate_ping_ids(objs)
+ # The map of a ping's name to its entry (a combination of a monotonic
+ # integer and its index in the string table)
+ pings = {}
+ for ping_name in objs["pings"].keys():
+ ping_id = get_ping_id(ping_name)
+ ping_name = util.camelize(ping_name)
+ pings[ping_name] = ping_entry(ping_id, ping_string_table.stringIndex(ping_name))
+
+ ping_map = [
+ (bytearray(ping_name, "ascii"), ping_entry)
+ for (ping_name, ping_entry) in pings.items()
+ ]
+ ping_string_table = ping_string_table.writeToString("gPingStringTable")
+ ping_phf = PerfectHash(ping_map, 64)
+ ping_by_name_lookup = ping_phf.cxx_codegen(
+ name="PingByNameLookup",
+ entry_type="ping_entry_t",
+ lower_entry=lambda x: str(x[1]),
+ key_type="const nsACString&",
+ key_bytes="aKey.BeginReading()",
+ key_length="aKey.Length()",
+ return_type="static Maybe<uint32_t>",
+ return_entry="return ping_result_check(aKey, entry);",
+ )
+
+ output_fd.write(
+ template.render(
+ ping_index_bits=PING_INDEX_BITS,
+ ping_by_name_lookup=ping_by_name_lookup,
+ ping_string_table=ping_string_table,
+ )
+ )
+ output_fd.write("\n")
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py b/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py
new file mode 100644
index 0000000000..3d4ac2f3cc
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+
+# 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 http://mozilla.org/MPL/2.0/.
+
+import cpp
+import js
+import re
+import rust
+import sys
+
+from glean_parser import lint, parser, util
+from pathlib import Path
+
+
+def get_parser_options(moz_app_version):
+ app_version_major = moz_app_version.split(".", 1)[0]
+ return {
+ "allow_reserved": False,
+ "custom_is_expired": lambda expires: expires == "expired"
+ or expires != "never"
+ and int(expires) <= int(app_version_major),
+ "custom_validate_expires": lambda expires: expires in ("expired", "never")
+ or re.fullmatch(r"\d\d+", expires, flags=re.ASCII),
+ }
+
+
+def parse(args):
+ """
+ Parse and lint the input files,
+ then return the parsed objects for further processing.
+ """
+
+ # Unfortunately, GeneratedFile appends `flags` directly after `inputs`
+ # instead of listifying either, so we need to pull stuff from a *args.
+ yaml_array = args[:-1]
+ moz_app_version = args[-1]
+
+ input_files = [Path(x) for x in yaml_array]
+
+ # Derived heavily from glean_parser.translate.translate.
+ # Adapted to how mozbuild sends us a fd, and to expire on versions not dates.
+
+ options = get_parser_options(moz_app_version)
+
+ # Lint the yaml first, then lint the metrics.
+ if lint.lint_yaml_files(input_files, parser_config=options):
+ # Warnings are Errors
+ sys.exit(1)
+
+ all_objs = parser.parse_objects(input_files, options)
+ if util.report_validation_errors(all_objs):
+ sys.exit(1)
+
+ if lint.lint_metrics(all_objs.value, options):
+ # Treat Warnings as Errors in FOG
+ sys.exit(1)
+
+ return all_objs.value, options
+
+
+# Must be kept in sync with the length of `deps` in moz.build.
+DEPS_LEN = 13
+
+
+def main(output_fd, *args):
+ args = args[DEPS_LEN:]
+ all_objs, options = parse(args)
+ rust.output_rust(all_objs, output_fd, options)
+
+
+def cpp_metrics(output_fd, *args):
+ args = args[DEPS_LEN:]
+ all_objs, options = parse(args)
+ cpp.output_cpp(all_objs, output_fd, options)
+
+
+def js_metrics(output_fd, *args):
+ args = args[DEPS_LEN:]
+ all_objs, options = parse(args)
+ js.output_js(all_objs, output_fd, options)
+
+
+if __name__ == "__main__":
+ main(sys.stdout, *sys.argv[1:])
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py b/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py
new file mode 100644
index 0000000000..8219aff97a
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/rust.py
@@ -0,0 +1,227 @@
+# -*- coding: utf-8 -*-
+
+# 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 http://mozilla.org/MPL/2.0/.
+
+"""
+Outputter to generate Rust code for metrics.
+"""
+
+import enum
+import json
+
+import jinja2
+
+from util import generate_metric_ids, generate_ping_ids
+from glean_parser import util
+
+
+def rust_datatypes_filter(value):
+ """
+ A Jinja2 filter that renders Rust literals.
+
+ Based on Python's JSONEncoder, but overrides:
+ - dicts and sets to raise an error
+ - sets to vec![] (used in labels)
+ - enums to become Class::Value
+ - lists to vec![] (used in send_in_pings)
+ - null to None
+ - strings to "value".into()
+ """
+
+ class RustEncoder(json.JSONEncoder):
+ def iterencode(self, value):
+ if isinstance(value, dict):
+ raise ValueError("RustEncoder doesn't know dicts {}".format(str(value)))
+ elif isinstance(value, enum.Enum):
+ yield (value.__class__.__name__ + "::" + util.Camelize(value.name))
+ elif isinstance(value, set):
+ yield "vec!["
+ first = True
+ for subvalue in sorted(list(value)):
+ if not first:
+ yield ", "
+ yield from self.iterencode(subvalue)
+ first = False
+ yield "]"
+ elif isinstance(value, list):
+ yield "vec!["
+ first = True
+ for subvalue in list(value):
+ if not first:
+ yield ", "
+ yield from self.iterencode(subvalue)
+ first = False
+ yield "]"
+ elif value is None:
+ yield "None"
+ elif isinstance(value, str):
+ yield '"' + value + '".into()'
+ else:
+ yield from super().iterencode(value)
+
+ return "".join(RustEncoder().iterencode(value))
+
+
+def ctor(obj):
+ """
+ Returns the scope and name of the constructor to use for a metric object.
+ Necessary because LabeledMetric<T> is constructed using LabeledMetric::new
+ not LabeledMetric<T>::new
+ """
+ if getattr(obj, "labeled", False):
+ return "LabeledMetric::new"
+ return class_name(obj.type) + "::new"
+
+
+def type_name(obj):
+ """
+ Returns the Rust type to use for a given metric or ping object.
+ """
+
+ if getattr(obj, "labeled", False):
+ return "LabeledMetric<{}>".format(class_name(obj.type))
+ generate_enums = getattr(obj, "_generate_enums", []) # Extra Keys? Reasons?
+ if len(generate_enums):
+ for name, suffix in generate_enums:
+ if not len(getattr(obj, name)) and suffix == "Keys":
+ return class_name(obj.type) + "<NoExtraKeys>"
+ else:
+ return "{}<{}>".format(
+ class_name(obj.type), util.Camelize(obj.name) + suffix
+ )
+ return class_name(obj.type)
+
+
+def class_name(obj_type):
+ """
+ Returns the Rust class name for a given metric or ping type.
+ """
+ if obj_type == "ping":
+ return "Ping"
+ if obj_type.startswith("labeled_"):
+ obj_type = obj_type[8:]
+ return util.Camelize(obj_type) + "Metric"
+
+
+def extra_keys(allowed_extra_keys):
+ """
+ Returns the &'static [&'static str] ALLOWED_EXTRA_KEYS for impl ExtraKeys
+ """
+ return "&[" + ", ".join(map(lambda key: '"' + key + '"', allowed_extra_keys)) + "]"
+
+
+def output_rust(objs, output_fd, options={}):
+ """
+ Given a tree of objects, output Rust code to the file-like object `output_fd`.
+
+ :param objs: A tree of objects (metrics and pings) as returned from
+ `parser.parse_objects`.
+ :param output_fd: Writeable file to write the output to.
+ :param options: options dictionary, presently unused.
+ """
+
+ # Monkeypatch a util.snake_case function for the templates to use
+ util.snake_case = lambda value: value.replace(".", "_").replace("-", "_")
+ # Monkeypatch util.get_jinja2_template to find templates nearby
+
+ def get_local_template(template_name, filters=()):
+ env = jinja2.Environment(
+ loader=jinja2.PackageLoader("rust", "templates"),
+ trim_blocks=True,
+ lstrip_blocks=True,
+ )
+ env.filters["camelize"] = util.camelize
+ env.filters["Camelize"] = util.Camelize
+ for filter_name, filter_func in filters:
+ env.filters[filter_name] = filter_func
+ return env.get_template(template_name)
+
+ util.get_jinja2_template = get_local_template
+ get_metric_id = generate_metric_ids(objs)
+ get_ping_id = generate_ping_ids(objs)
+
+ # Map from a tuple (const, typ) to an array of tuples (id, path)
+ # where:
+ # const: The Rust constant name to be used for the lookup map
+ # typ: The metric type to be stored in the lookup map
+ # id: The numeric metric ID
+ # path: The fully qualified path to the metric object in Rust
+ #
+ # This map is only filled for metrics, not for pings.
+ #
+ # Example:
+ #
+ # ("COUNTERS", "CounterMetric") -> [(1, "test_only::clicks"), ...]
+ objs_by_type = {}
+
+ # Map from a metric ID to the fully qualified path of the event object in Rust.
+ # Required for the special handling of event lookups.
+ #
+ # Example:
+ #
+ # 17 -> "test_only::an_event"
+ events_by_id = {}
+
+ if len(objs) == 1 and "pings" in objs:
+ template_filename = "rust_pings.jinja2"
+ else:
+ template_filename = "rust.jinja2"
+
+ for category_name, metrics in objs.items():
+ for metric in metrics.values():
+
+ # The constant is all uppercase and suffixed by `_MAP`
+ const_name = util.snake_case(metric.type).upper() + "_MAP"
+ typ = type_name(metric)
+ key = (const_name, typ)
+
+ metric_name = util.snake_case(metric.name)
+ category_name = util.snake_case(category_name)
+ full_path = f"{category_name}::{metric_name}"
+
+ if metric.type == "event":
+ events_by_id[get_metric_id(metric)] = full_path
+ continue
+
+ if key not in objs_by_type:
+ objs_by_type[key] = []
+ objs_by_type[key].append((get_metric_id(metric), full_path))
+
+ # Now for the modules for each category.
+ template = util.get_jinja2_template(
+ template_filename,
+ filters=(
+ ("rust", rust_datatypes_filter),
+ ("snake_case", util.snake_case),
+ ("type_name", type_name),
+ ("ctor", ctor),
+ ("extra_keys", extra_keys),
+ ("metric_id", get_metric_id),
+ ("ping_id", get_ping_id),
+ ),
+ )
+
+ # The list of all args to CommonMetricData (besides category and name).
+ # No particular order is required, but I have these in common_metric_data.rs
+ # order just to be organized.
+ common_metric_data_args = [
+ "name",
+ "category",
+ "send_in_pings",
+ "lifetime",
+ "disabled",
+ "dynamic_label",
+ ]
+
+ output_fd.write(
+ template.render(
+ all_objs=objs,
+ common_metric_data_args=common_metric_data_args,
+ metric_by_type=objs_by_type,
+ extra_args=util.extra_args,
+ events_by_id=events_by_id,
+ )
+ )
+ output_fd.write("\n")
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/string_table.py b/toolkit/components/glean/build_scripts/glean_parser_ext/string_table.py
new file mode 100644
index 0000000000..1958eef604
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/string_table.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+
+# 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 http://mozilla.org/MPL/2.0/.
+
+from io import StringIO
+
+
+class StringTable:
+ """Manages a string table and allows C style serialization to a file."""
+
+ def __init__(self):
+ self.current_index = 0
+ self.table = {}
+
+ def c_strlen(self, string):
+ """The length of a string including the null terminating character.
+ :param string: the input string.
+ """
+ return len(string) + 1
+
+ def stringIndex(self, string):
+ """Returns the index in the table of the provided string. Adds the string to
+ the table if it's not there.
+ :param string: the input string.
+ """
+ if string in self.table:
+ return self.table[string]
+ result = self.current_index
+ self.table[string] = result
+ self.current_index += self.c_strlen(string)
+ return result
+
+ def stringIndexes(self, strings):
+ """Returns a list of indexes for the provided list of strings.
+ Adds the strings to the table if they are not in it yet.
+ :param strings: list of strings to put into the table.
+ """
+ return [self.stringIndex(s) for s in strings]
+
+ def writeToString(self, name):
+ """Writes the string table to a string as a C const char array.
+
+ See `writeDefinition` for details
+
+ :param name: the name of the output array.
+ """
+
+ output = StringIO()
+ self.writeDefinition(output, name)
+ return output.getvalue()
+
+ def writeDefinition(self, f, name):
+ """Writes the string table to a file as a C const char array.
+
+ This writes out the string table as one single C char array for memory
+ size reasons, separating the individual strings with '\0' characters.
+ This way we can index directly into the string array and avoid the additional
+ storage costs for the pointers to them (and potential extra relocations for those).
+
+ :param f: the output stream.
+ :param name: the name of the output array.
+ """
+ entries = self.table.items()
+
+ # Avoid null-in-string warnings with GCC and potentially
+ # overlong string constants; write everything out the long way.
+ def explodeToCharArray(string):
+ def toCChar(s):
+ if s == "'":
+ return "'\\''"
+ return "'%s'" % s
+
+ return ", ".join(map(toCChar, string))
+
+ f.write("#if defined(_MSC_VER) && !defined(__clang__)\n")
+ f.write("const char %s[] = {\n" % name)
+ f.write("#else\n")
+ f.write("constexpr char %s[] = {\n" % name)
+ f.write("#endif\n")
+ for (string, offset) in sorted(entries, key=lambda x: x[1]):
+ if "*/" in string:
+ raise ValueError(
+ "String in string table contains unexpected sequence '*/': %s"
+ % string
+ )
+
+ e = explodeToCharArray(string)
+ if e:
+ f.write(
+ " /* %5d - \"%s\" */ %s, '\\0',\n"
+ % (offset, string, explodeToCharArray(string))
+ )
+ else:
+ f.write(" /* %5d - \"%s\" */ '\\0',\n" % (offset, string))
+ f.write("};\n\n")
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2
new file mode 100644
index 0000000000..2eaecd237f
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp.jinja2
@@ -0,0 +1,55 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+{# The rendered source is autogenerated, but this
+Jinja2 template is not. Please file bugs! #}
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Metrics_h
+#define mozilla_Metrics_h
+
+#include "mozilla/glean/bindings/MetricTypes.h"
+
+namespace mozilla::glean {
+
+enum class NoExtraKeys {};
+
+{% macro generate_extra_keys(obj) %}
+{% for name, suffix in obj["_generate_enums"] %}
+{% if obj|attr(name)|length %}
+ enum class {{ obj.name|Camelize }}{{ suffix }} : int32_t {
+ {% for key in obj|attr(name) %}
+ {{ key | Camelize }},
+ {% endfor %}
+ };
+
+{% endif %}
+{% endfor %}
+{% endmacro %}
+
+{% for category_name, objs in all_objs.items() %}
+namespace {{ category_name|snake_case }} {
+ {% for obj in objs.values() %}
+ {% if obj.type|is_implemented_type %}
+ /**
+ * generated from {{ category_name }}.{{ obj.name }}
+ */
+ {% if obj|attr("_generate_enums") %}
+{{ generate_extra_keys(obj) }}
+ {%- endif %}
+ /**
+ * {{ obj.description|wordwrap() | replace('\n', '\n * ') }}
+ */
+ constexpr impl::{{ obj|type_name }} {{obj.name|snake_case }}({{obj|metric_id}});
+
+ {% endif %}
+ {% endfor %}
+}
+{% endfor %}
+
+} // namespace mozilla::glean
+
+#endif // mozilla_Metrics_h
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp_pings.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp_pings.jinja2
new file mode 100644
index 0000000000..f877cb9685
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/cpp_pings.jinja2
@@ -0,0 +1,30 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+{# The rendered source is autogenerated, but this
+Jinja2 template is not. Please file bugs! #}
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_Pings_h
+#define mozilla_glean_Pings_h
+
+#include "mozilla/glean/bindings/Ping.h"
+
+namespace mozilla::glean_pings {
+
+{% for obj in all_objs['pings'].values() %}
+/*
+ * Generated from {{ obj.name }}.
+ *
+ * {{ obj.description|wordwrap() | replace('\n', '\n * ') }}
+ */
+constexpr glean::impl::Ping {{ obj.name|Camelize }}({{ obj.name|ping_id }});
+
+{% endfor %}
+
+} // namespace mozilla::glean_pings
+
+#endif // mozilla_glean_Pings_h
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/js.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/js.jinja2
new file mode 100644
index 0000000000..b8804b91e5
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/js.jinja2
@@ -0,0 +1,129 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+{# The rendered source is autogenerated, but this
+Jinja2 template is not. Please file bugs! #}
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GleanJSMetricsLookup_h
+#define mozilla_GleanJSMetricsLookup_h
+
+#include "mozilla/PerfectHash.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/glean/bindings/MetricTypes.h"
+
+#define GLEAN_INDEX_BITS ({{index_bits}})
+#define GLEAN_ID_BITS ({{id_bits}})
+#define GLEAN_TYPE_ID(id) ((id) >> GLEAN_ID_BITS)
+#define GLEAN_METRIC_ID(id) ((id) & ((1ULL << GLEAN_ID_BITS) - 1))
+#define GLEAN_OFFSET(entry) (entry & ((1ULL << GLEAN_INDEX_BITS) - 1))
+
+namespace mozilla::glean {
+
+// The category lookup table's entry type
+using category_entry_t = uint32_t;
+// The metric lookup table's entry type
+using metric_entry_t = uint64_t;
+
+static_assert(GLEAN_INDEX_BITS + GLEAN_ID_BITS < sizeof(metric_entry_t) * 8, "Index and ID bits need to fit into an category_entry_t");
+static_assert(GLEAN_ID_BITS < sizeof(uint32_t) * 8, "Metric IDs need to fit into less than 32 bit");
+static_assert({{ categories|length }} < UINT32_MAX, "Too many metric categories generated.");
+static_assert({{ metric_id_mapping|length }} < {{2 ** id_bits}}, "Too many metrics generated.");
+static_assert({{ metric_type_ids|length }} < {{2 ** (entry_width - index_bits - id_bits)}}, "Too many different metric types.");
+
+static already_AddRefed<nsISupports> NewMetricFromId(uint32_t id) {
+ uint32_t typeId = GLEAN_TYPE_ID(id);
+ uint32_t metricId = GLEAN_METRIC_ID(id);
+
+ switch (typeId) {
+ {% for type, type_id in metric_type_ids.items() %}
+ {% if type|is_implemented_type %}
+ case {{ type_id }}: /* {{ type|Camelize }} */
+ {
+ return MakeAndAddRef<{{type | type_name}}>(metricId);
+ }
+ {% endif %}
+ {% endfor %}
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid type ID reached when trying to instantiate a new metric");
+ return nullptr;
+ }
+}
+
+static Maybe<uint32_t> category_result_check(const nsACString& aKey, category_entry_t entry);
+static Maybe<uint32_t> metric_result_check(const nsACString& aKey, metric_entry_t entry);
+
+{{ category_string_table }}
+static_assert(sizeof(gCategoryStringTable) < UINT32_MAX, "Category string table is too large.");
+
+{{ category_by_name_lookup }}
+
+{{ metric_string_table }}
+static_assert(sizeof(gMetricStringTable) < {{2 ** index_bits}}, "Metric string table is too large.");
+
+{{ metric_by_name_lookup }}
+
+/**
+ * Get a category's name from the string table.
+ */
+static const char* GetCategoryName(category_entry_t entry) {
+ MOZ_ASSERT(entry < sizeof(gCategoryStringTable), "Entry identifier offset larger than string table");
+ return &gCategoryStringTable[entry];
+}
+
+/**
+ * Get a metric's identifier from the string table.
+ */
+static const char* GetMetricIdentifier(metric_entry_t entry) {
+ uint32_t offset = GLEAN_OFFSET(entry);
+ MOZ_ASSERT(offset < sizeof(gMetricStringTable), "Entry identifier offset larger than string table");
+ return &gMetricStringTable[offset];
+}
+
+/**
+ * Check that the found entry is pointing to the right key
+ * and return it.
+ * Or return `Nothing()` if the entry was not found.
+ */
+static Maybe<uint32_t> category_result_check(const nsACString& aKey, category_entry_t entry) {
+ if (MOZ_UNLIKELY(entry > sizeof(gCategoryStringTable))) {
+ return Nothing();
+ }
+ if (aKey.EqualsASCII(gCategoryStringTable + entry)) {
+ return Some(entry);
+ }
+ return Nothing();
+}
+
+/**
+ * Check if the found entry index is pointing to the right key
+ * and return the corresponding metric ID.
+ * Or return `Nothing()` if the entry was not found.
+ */
+static Maybe<uint32_t> metric_result_check(const nsACString& aKey, uint64_t entry) {
+ uint32_t metricId = entry >> GLEAN_INDEX_BITS;
+ uint32_t offset = GLEAN_OFFSET(entry);
+
+ if (offset > sizeof(gMetricStringTable)) {
+ return Nothing();
+ }
+
+ if (aKey.EqualsASCII(gMetricStringTable + offset)) {
+ return Some(metricId);
+ }
+
+ return Nothing();
+}
+
+
+#undef GLEAN_INDEX_BITS
+#undef GLEAN_ID_BITS
+#undef GLEAN_TYPE_ID
+#undef GLEAN_METRIC_ID
+#undef GLEAN_OFFSET
+
+} // namespace mozilla::glean
+#endif // mozilla_GleanJSMetricsLookup_h
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/js_pings.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/js_pings.jinja2
new file mode 100644
index 0000000000..85665ec558
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/js_pings.jinja2
@@ -0,0 +1,64 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+{# The rendered source is autogenerated, but this
+Jinja2 template is not. Please file bugs! #}
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GleanJSPingsLookup_h
+#define mozilla_GleanJSPingsLookup_h
+
+#define GLEAN_PING_INDEX_BITS ({{ping_index_bits}})
+#define GLEAN_PING_ID(entry) ((entry) >> GLEAN_PING_INDEX_BITS)
+#define GLEAN_PING_INDEX(entry) ((entry) & ((1UL << GLEAN_PING_INDEX_BITS) - 1))
+
+namespace mozilla::glean {
+
+// Contains the ping id and the index into the ping string table.
+using ping_entry_t = uint32_t;
+
+static Maybe<uint32_t> ping_result_check(const nsACString& aKey, ping_entry_t aEntry);
+
+{{ ping_string_table }}
+
+{{ ping_by_name_lookup }}
+
+/**
+ * Get a ping's name given its entry from the PHF.
+ */
+static const char* GetPingName(ping_entry_t aEntry) {
+ uint32_t idx = GLEAN_PING_INDEX(aEntry);
+ MOZ_ASSERT(idx < sizeof(gPingStringTable), "Ping index larger than string table");
+ return &gPingStringTable[idx];
+}
+
+/**
+ * Check if the found entry is pointing at the correct ping.
+ * PHF can false-positive a result when the key isn't present, so we check
+ * for a string match. If it fails, return Nothing(). If we found it,
+ * return the ping's id.
+ */
+static Maybe<uint32_t> ping_result_check(const nsACString& aKey, ping_entry_t aEntry) {
+ uint32_t idx = GLEAN_PING_INDEX(aEntry);
+ uint32_t id = GLEAN_PING_ID(aEntry);
+
+ if (MOZ_UNLIKELY(idx > sizeof(gPingStringTable))) {
+ return Nothing();
+ }
+
+ if (aKey.EqualsASCII(&gPingStringTable[idx])) {
+ return Some(id);
+ }
+
+ return Nothing();
+}
+
+#undef GLEAN_PING_INDEX_BITS
+#undef GLEAN_PING_ID
+#undef GLEAN_PING_INDEX
+
+} // namespace mozilla::glean
+#endif // mozilla_GleanJSPingsLookup_h
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2
new file mode 100644
index 0000000000..ac86d2c6de
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust.jinja2
@@ -0,0 +1,217 @@
+// -*- mode: Rust -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+{# The rendered source is autogenerated, but this
+Jinja2 template is not. Please file bugs! #}
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+{% macro generate_extra_keys(obj) %}
+{% for name, suffix in obj["_generate_enums"] %}
+{% if obj|attr(name)|length %}
+ #[derive(Clone, Copy, Hash, Eq, PartialEq)]
+ pub enum {{ obj.name|Camelize }}{{ suffix }} {
+ {% for key in obj|attr(name) %}
+ {{ key | Camelize }},
+ {% endfor %}
+ }
+
+ impl ExtraKeys for {{ obj.name|Camelize }}{{ suffix }} {
+ const ALLOWED_KEYS: &'static [&'static str] = {{ obj.allowed_extra_keys|extra_keys }};
+
+ fn index(self) -> i32 {
+ self as i32
+ }
+ }
+
+ /// Convert from an extra key's index to its variant.
+ impl std::convert::TryFrom<i32> for {{ obj.name|Camelize }}{{ suffix }} {
+ type Error = EventRecordingError;
+
+ fn try_from(value: i32) -> Result<Self, Self::Error> {
+ match value {
+ {% for key in obj|attr(name) %}
+ {{loop.index-1}} => Ok(Self::{{key | Camelize}}),
+ {% endfor %}
+ _ => Err(EventRecordingError::InvalidExtraKey),
+ }
+ }
+ }
+
+ /// Convert from an extra key's string representation to its variant.
+ impl std::convert::TryFrom<&str> for {{ obj.name|Camelize }}{{ suffix }} {
+ type Error = EventRecordingError;
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ match value {
+ {% for key in obj|attr(name) %}
+ "{{key}}" => Ok(Self::{{key | Camelize}}),
+ {% endfor %}
+ _ => Err(EventRecordingError::InvalidExtraKey),
+ }
+ }
+ }
+
+{% endif %}
+{% endfor %}
+{% endmacro %}
+
+{% for category_name, objs in all_objs.items() %}
+pub mod {{ category_name|snake_case }} {
+ use crate::private::*;
+ use glean_core::CommonMetricData;
+ use once_cell::sync::Lazy;
+
+ {% for obj in objs.values() %}
+ {% if obj|attr("_generate_enums") %}
+{{ generate_extra_keys(obj) }}
+ {%- endif %}
+ #[allow(non_upper_case_globals)]
+ /// generated from {{ category_name }}.{{ obj.name }}
+ ///
+ /// {{ obj.description|wordwrap() | replace('\n', '\n /// ') }}
+ pub static {{ obj.name|snake_case }}: Lazy<{{ obj|type_name }}> = Lazy::new(|| {
+ {{ obj|ctor }}({{obj|metric_id}}.into(), CommonMetricData {
+ {% for arg_name in common_metric_data_args if obj[arg_name] is defined %}
+ {{ arg_name }}: {{ obj[arg_name]|rust }},
+ {% endfor %}
+ ..Default::default()
+ }
+ {%- for arg_name in extra_args if obj[arg_name] is defined and arg_name not in common_metric_data_args and arg_name != 'allowed_extra_keys' -%}
+ , {{ obj[arg_name]|rust }}
+ {%- endfor -%}
+ {{ ", " if obj.labeled else ")\n" }}
+ {%- if obj.labeled -%}
+ {%- if obj.labels -%}
+ Some({{ obj.labels|rust }})
+ {%- else -%}
+ None
+ {%- endif -%})
+ {% endif %}
+ });
+
+ {% endfor %}
+}
+{% endfor %}
+
+{% if metric_by_type|length > 0 %}
+#[allow(dead_code)]
+pub(crate) mod __glean_metric_maps {
+ use std::collections::HashMap;
+ use std::convert::TryInto;
+
+ use crate::private::*;
+ use once_cell::sync::Lazy;
+
+{% for typ, metrics in metric_by_type.items() %}
+ pub static {{typ.0}}: Lazy<HashMap<MetricId, &Lazy<{{typ.1}}>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity({{metrics|length}});
+ {% for metric in metrics %}
+ map.insert({{metric.0}}.into(), &super::{{metric.1}});
+ {% endfor %}
+ map
+ });
+
+{% endfor %}
+
+ /// 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()
+ }
+
+ /// Wrapper to record an event based on its metric ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `extra` - An (optional) map of (extra key id, string) pairs.
+ /// The map will be decoded into the appropriate `ExtraKeys` type.
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the event was found and `record` was called with the given `extra`,
+ /// or an `EventRecordingError::InvalidId` if no event by that ID exists
+ /// or an `EventRecordingError::InvalidExtraKey` the `extra` map could not be deserialized.
+ pub(crate) fn event_record_wrapper(metric_id: u32, extra: HashMap<i32, String>) -> Result<(), EventRecordingError> {
+ match metric_id {
+{% for metric_id, event in events_by_id.items() %}
+ {{metric_id}} => {
+ assert!(
+ extra_keys_len(&super::{{event}}) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ // In case of `NoExtraKeys` the whole iterator is impossible, so rustc complains.
+ #[allow(unused_variables)]
+ let extra: HashMap<_, _> = extra
+ .into_iter()
+ .map(|(k, v)| k.try_into().map(|k| (k, v)))
+ .collect::<Result<HashMap<_, _>, _>>()?;
+ super::{{event}}.record(Some(extra));
+ Ok(())
+ }
+{% endfor %}
+ _ => Err(EventRecordingError::InvalidId),
+ }
+ }
+
+ /// Wrapper to record an event based on its metric ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `extra` - An (optional) map of (string, string) pairs.
+ /// The map will be decoded into the appropriate `ExtraKeys` types.
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the event was found and `record` was called with the given `extra`,
+ /// or an `EventRecordingError::InvalidId` if no event by that ID exists
+ /// or an `EventRecordingError::InvalidExtraKey` the `extra` map could not be deserialized.
+ pub(crate) fn event_record_wrapper_str(metric_id: u32, extra: HashMap<String, String>) -> Result<(), EventRecordingError> {
+ match metric_id {
+{% for metric_id, event in events_by_id.items() %}
+ {{metric_id}} => {
+ assert!(
+ extra_keys_len(&super::{{event}}) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ // In case of `NoExtraKeys` the whole iterator is impossible, so rustc complains.
+ #[allow(unused_variables)]
+ let extra = extra
+ .into_iter()
+ .map(|(k, v)| (&*k).try_into().map(|k| (k, v)))
+ .collect::<Result<HashMap<_, _>, _>>()?;
+ super::{{event}}.record(Some(extra));
+ Ok(())
+ }
+{% endfor %}
+ _ => Err(EventRecordingError::InvalidId),
+ }
+ }
+
+ /// Wrapper to get the currently stored events for event metric.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `storage_name` - the storage name to look into.
+ ///
+ /// # Returns
+ ///
+ /// Returns the recorded events or `None` if nothing stored.
+ ///
+ /// # Panics
+ ///
+ /// Panics if no event by the given metric ID could be found.
+ pub(crate) fn event_test_get_value_wrapper(metric_id: u32, storage_name: &str) -> Option<Vec<RecordedEvent>> {
+ match metric_id {
+{% for metric_id, event in events_by_id.items() %}
+ {{metric_id}} => super::{{event}}.test_get_value(storage_name),
+{% endfor %}
+ _ => panic!("No event for metric id {}", metric_id),
+ }
+ }
+}
+{% endif %}
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2 b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2
new file mode 100644
index 0000000000..a570cd9b79
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/templates/rust_pings.jinja2
@@ -0,0 +1,47 @@
+// -*- mode: Rust -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+{# The rendered source is autogenerated, but this
+Jinja2 template is not. Please file bugs! #}
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use crate::private::Ping;
+use once_cell::sync::Lazy;
+
+{% for obj in all_objs['pings'].values() %}
+#[allow(non_upper_case_globals)]
+/// {{ obj.description|wordwrap() | replace('\n', '\n/// ') }}
+pub static {{ obj.name|snake_case }}: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "{{ obj.name }}",
+ {{ obj.include_client_id|rust }},
+ {{ obj.send_if_empty|rust }},
+ {{ obj.reason_codes|rust }},
+ )
+});
+
+{% endfor %}
+
+/// Instantiate each custom ping once to trigger registration.
+#[doc(hidden)]
+pub fn register_pings() {
+ {% for obj in all_objs['pings'].values() %}
+ let _ = &*{{obj.name|snake_case }};
+ {% endfor %}
+}
+
+#[cfg(feature = "with_gecko")]
+pub(crate) fn submit_ping_by_id(id: u32, reason: Option<&str>) {
+ match id {
+{% for obj in all_objs['pings'].values() %}
+ {{ obj.name|ping_id }} => {{ obj.name | snake_case }}.submit(reason),
+{% endfor %}
+ _ => {
+ // TODO: instrument this error.
+ log::error!("Cannot submit unknown ping {} by id.", id);
+ }
+ }
+}
diff --git a/toolkit/components/glean/build_scripts/glean_parser_ext/util.py b/toolkit/components/glean/build_scripts/glean_parser_ext/util.py
new file mode 100644
index 0000000000..19b3d62bc0
--- /dev/null
+++ b/toolkit/components/glean/build_scripts/glean_parser_ext/util.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+# 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 http://mozilla.org/MPL/2.0/.
+
+"""
+Utitlity functions for the glean_parser-based code generator
+"""
+
+
+def generate_ping_ids(objs):
+ """
+ Return a lookup function for ping IDs per ping name.
+
+ :param objs: A tree of objects as returned from `parser.parse_objects`.
+ """
+
+ if "pings" not in objs:
+
+ def no_ping_ids_for_you():
+ assert False
+
+ return no_ping_ids_for_you
+
+ # Ping ID 0 is reserved (but unused) right now.
+ ping_id = 1
+
+ ping_id_mapping = {}
+ for ping_name in objs["pings"].keys():
+ ping_id_mapping[ping_name] = ping_id
+ ping_id += 1
+
+ return lambda ping_name: ping_id_mapping[ping_name]
+
+
+def generate_metric_ids(objs):
+ """
+ Return a lookup function for metric IDs per metric object.
+
+ :param objs: A tree of metrics as returned from `parser.parse_objects`.
+ """
+
+ # Metric ID 0 is reserved (but unused) right now.
+ metric_id = 1
+
+ # Mapping from a tuple of (category name, metric name) to the metric's numeric ID
+ metric_id_mapping = {}
+ for category_name, metrics in objs.items():
+ for metric in metrics.values():
+ metric_id_mapping[(category_name, metric.name)] = metric_id
+ metric_id += 1
+
+ return lambda metric: metric_id_mapping[(metric.category, metric.name)]
+
+
+IMPLEMENTED_CPP_TYPES = [
+ "boolean",
+ "counter",
+ "datetime",
+ "event",
+ "memory_distribution",
+ "string",
+ "string_list",
+ "timespan",
+ "timing_distribution",
+ "uuid",
+]
+
+
+def is_implemented_metric_type(typ):
+ """
+ Filter out some unimplemented metric types to avoid generating C++ code for them.
+ Once all types are implemented this code will be removed.
+ """
+ return typ in IMPLEMENTED_CPP_TYPES
diff --git a/toolkit/components/glean/cbindgen.toml b/toolkit/components/glean/cbindgen.toml
new file mode 100644
index 0000000000..de4046e0f2
--- /dev/null
+++ b/toolkit/components/glean/cbindgen.toml
@@ -0,0 +1,19 @@
+header = """/* 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 http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla::glean::impl"]
+includes = ["nsTArray.h", "nsString.h"]
+
+[export.rename]
+"ThinVec" = "nsTArray"
+
+[parse]
+parse_deps = true
+include = ["fog"]
+extra_bindings = ["fog"]
diff --git a/toolkit/components/glean/docs/api.md b/toolkit/components/glean/docs/api.md
new file mode 100644
index 0000000000..8561d11d3b
--- /dev/null
+++ b/toolkit/components/glean/docs/api.md
@@ -0,0 +1,4 @@
+# API
+
+The Metrics API documentation can be found in the
+[Glean SDK Book](https://mozilla.github.io/glean/book/user/metrics/index.html).
diff --git a/toolkit/components/glean/docs/code_organization.md b/toolkit/components/glean/docs/code_organization.md
new file mode 100644
index 0000000000..2055fd0469
--- /dev/null
+++ b/toolkit/components/glean/docs/code_organization.md
@@ -0,0 +1,64 @@
+# FOG code organization
+
+```eval_rst
+.. note::
+
+ Project FOG is currently being designed and implemented.
+ This documentation is incomplete and may change significantly before FOG is usable inside mozilla-central.
+```
+
+![Modules of Project FOG](images/fog-modules.svg)
+
+The diagram shows the different modules of Project FOG.
+
+## FOG control
+
+This module is the glue between Firefox and Glean.
+
+* The code lives in `toolkit/components/glean/src`.
+* It is written in Rust.
+* The crate is named `fog_control`.
+* It is not published to crates.io.
+* It is not consumed by other Rust crates inside mozilla-central.
+
+This module is responsible for
+
+* collecting and assembling the [client information](https://mozilla.github.io/glean/book/user/pings/index.html#the-client_info-section)
+* configuring Glean
+* watching the Firefox Telemetry data upload preference (`datareporting.healthreport.uploadEnabled`)
+* scheduling builtin pings
+* controling ping upload workers
+* passing IPC buffers
+
+It calls into `glean_core` to:
+
+* configure and initialize Glean
+* toggle `upload_enabled`
+* get upload tasks
+
+It calls into `fog` to:
+
+* pass IPC buffers
+* record to its own metrics
+
+## FOG API
+
+This module provides the user-facing API for Glean inside mozilla-central.
+
+* The code lives in `toolkit/components/glean/api`.
+* It is written in Rust.
+* The crate is named `fog`.
+* It is not published to crates.io.
+* It can be consumed by other Rust crates inside mozilla-central for their Glean usage.
+* It will provide a build task for `glean_parser` integration.
+
+This module is responsible for
+
+* exposing a specific metric API in Rust
+* wrapping metric implementations for handling IPC
+* exposing FFI functionality to implement other language APIs on top
+
+It calls into `glean_core` for:
+
+* metric types (including pings)
+* querying `upload_enabled` status.
diff --git a/toolkit/components/glean/docs/images/fog-modules.svg b/toolkit/components/glean/docs/images/fog-modules.svg
new file mode 100644
index 0000000000..2b188c67a1
--- /dev/null
+++ b/toolkit/components/glean/docs/images/fog-modules.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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 http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" style="background-color: rgb(255, 255, 255);" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="601px" height="511px" viewBox="-0.5 -0.5 601 511" content="&lt;mxfile host=&quot;app.diagrams.net&quot; modified=&quot;2020-03-27T09:36:49.445Z&quot; agent=&quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:76.0) Gecko/20100101 Firefox/76.0&quot; etag=&quot;_te5ruI7nKv5hlBG0WM7&quot; version=&quot;12.9.3&quot; type=&quot;device&quot;&gt;&lt;diagram id=&quot;02tFoor6QwIjLFNeosYU&quot; name=&quot;Page-1&quot;&gt;7Vtbd6M2EP41fkyPQYDxo+NcmnbbpOvu2eZRBtmmxYgVsmP311eAZC6SDU4AX5ok5wSNLsA334xGI9ED4+XmkcBw8Rt2kd/T++6mB+56uq4NLYP9iyVbLtHsYSqZE8/lskww8f5FXNjn0pXnoqjQkGLsUy8sCh0cBMihBRkkBL8Vm82wX7xrCOdIEkwc6MvS755LF6nUNvuZ/GfkzRfizlqf1yyhaMwF0QK6+C0nAvc9MCYY0/RquRkjP0ZP4JL2e9hTu3swggJap0P/YUNGP0L/5QvY+N8XzwT/Y93wUdbQX/EX5g9LtwIBgleBi+JB+j1w+7bwKJqE0Ilr35jSmWxBlz4raexy5vn+GPuYJH3BbDbTHYfJI8puh3I1rjW1TIvVyK8hngkRijY5EX+tR4SXiJItayJqBcScZBYvvmUKs4RaFnlliX6Qk2S+GzrDkV1wKI+AVW8XVtdEtmuoYLX1KbBaglWza+IKQFu4AgWuls9ue+t66/iGvjcPkgrrxyo2rFuSPNauyK7m/H/SbYYZPHm9iIZxxU2UuKMRa6BZ4UYe5Q6tkY9DRGLPAh0vmItx2dulQxdvx8TJgwppiRNMMbSo+KKCAxygEhu4SLx4+rrgNtayx7zYiMuXnuvGd1ESrUjFBphj2pUGqRkq4rTFG6OCN0frv2umPQUUkQD60SfBEoINq12TBrpkmGZKYCOXRRK8iAld4DlmGrzPpCVcsjZfMA65fv5GlG55WARXFBe1hzYe/Svu/pPJS6+5mrsNHzkpbEUhYO+b6xQXX/N1WbektK1SW4RXxEEHsLF43AbJHPGuT9/MP23zj8lo8818CbSvweDGFsFIjNtBEhDkQ+qtixGaSqNJ1xEhcJtrEGIvoFFu5JdYkHELDErcGpbiqlJ73TjYnl2kT5Bxa/cq76ebJTk0Ns5t8tdk+GY7SB2+TW3TMBsyZqAVARwqbFlX2LLVli0PJXB/gWs4cYgX0qNdah6sffAK5+qg2M2f3Lsa9ukUonQMxqdv3Tvv6BflXM3DzvI8nKvAtJ4DOMrBXrh1quGyPs1zLzYiZ5UzT3VDcBb2qfdL9jaosM/D7VuyT1XWKrecSxc/X1cRrbks+lAuBiJ7poyRLMdG01lDuRjj3IxeTsU8PD8ywejlieEMlzFqwTQK08Vpqdy/AhXs1pInC4tqZBk7cMTvd44CrErnqDcevHwsu9vfw/0xZtMG9q+A3IZxagejy07+0UcwYKLJ3a8XCLE+PDuIZQeSTpoXh+1uG6IKy/Z2fOT58HIzMlYpqqs707WHrimh+/Qyzr3mg4zyAi+nq6iTJVk5g7UDIgeYrcDLbg2vQY3QIHBH8QY5Kzk+jCLPKS2w3rlYes/C7AMxhNl0DJHTmanQmZAdtw6TFk5GycTMspdP35z3OrACM8zSQFppoBQZaaCmFmO63STVCkTLeFdBNS1HtIx2TVNNLHmqqWZ+Uq0VqsmJ+df7icS2j29ynkkeHpTw3k0zOd6ottFbm4ZFHJBTwO/PV4x/KWLXFRF7t/i3fVzLjH+VwXry00tPK+Tk6U87iW1V2KmrNvV3ScjmAa+TYmk7juogihKoVk5toG6a+n8ytR29/1V2KVYH+XJwmg2azjdbQN0ADRg1WdxNPhHIuQNKYBDNkoOFKHCwmxwtvLi0jAlKbB/IHr3bRAKwT2IKF7FXKayi2nzqrm86Mh/5dGeaHIrY1NxsfNRJ9q1sNaqwv1urMcCn1VQZQ7XVDM/LavalVAly1tdgNapD591aTY3V2lnvzzbP2D3Bdyl9bgy6Cb6NisMt+56r3WBdTnLlgkEXXU0waJx8z86QV/epC/ThNkb7WvNaVo2TLIOWFHHobKriPFftr2Y01VczsS7T80jqb2YubpLbqUBorr2MMCtm36amzi37xBfc/wc=&lt;/diagram&gt;&lt;/mxfile&gt;"><defs/><g><rect x="0" y="60" width="600" height="120" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/><rect x="0" y="180" width="600" height="330" fill="#d5e8d4" stroke="#82b366" pointer-events="all"/><rect x="460" y="60" width="140" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-end; width: 138px; height: 1px; padding-top: 75px; margin-left: 460px;"><div style="box-sizing: border-box; font-size: 0; text-align: right; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><div align="right"><font style="font-size: 16px">Developer facing</font></div></div></div></div></foreignObject><text x="598" y="79" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="end">Developer facing</text></switch></g><rect x="470" y="180" width="130" height="30" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-end; width: 128px; height: 1px; padding-top: 195px; margin-left: 470px;"><div style="box-sizing: border-box; font-size: 0; text-align: right; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><div style="font-size: 16px" align="right"><font style="font-size: 16px">Internals</font></div></div></div></div></foreignObject><text x="598" y="199" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="end">Internals</text></switch></g><path d="M 250 150 L 250 190 L 120 190 L 120 203.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 120 208.88 L 116.5 201.88 L 120 203.63 L 123.5 201.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="190" y="90" width="120" height="60" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 120px; margin-left: 191px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">C++</div></div></div></foreignObject><text x="250" y="124" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">C++</text></switch></g><rect x="360" y="90" width="120" height="60" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 120px; margin-left: 361px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">JavaScript</div></div></div></foreignObject><text x="420" y="124" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">JavaScript</text></switch></g><path d="M 420 150 L 420 190 L 120 190 L 120 203.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 120 208.88 L 116.5 201.88 L 120 203.63 L 123.5 201.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="360" y="90" width="120" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 120px; margin-left: 361px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">JavaScript</div></div></div></foreignObject><text x="420" y="124" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">JavaScript</text></switch></g><path d="M 80 150 L 80 170 L 80 223.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 80 228.88 L 76.5 221.88 L 80 223.63 L 83.5 221.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="20" y="90" width="120" height="60" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 120px; margin-left: 21px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><div>Rust</div></div></div></div></foreignObject><text x="80" y="124" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Rust</text></switch></g><rect x="20" y="230" width="120" height="60" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 260px; margin-left: 21px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">FOG API   </div></div></div></foreignObject><text x="80" y="264" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">FOG API   </text></switch></g><path d="M 140 470 L 160 470 L 150 470 L 163.63 470" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 168.88 470 L 161.88 473.5 L 163.63 470 L 161.88 466.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="20" y="440" width="120" height="60" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 470px; margin-left: 21px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">FOG Control</div></div></div></foreignObject><text x="80" y="474" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">FOG Control</text></switch></g><rect x="170" y="440" width="120" height="60" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 470px; margin-left: 171px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Glean SDK</div></div></div></foreignObject><text x="230" y="474" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Glean SDK</text></switch></g><rect x="480" y="0" width="120" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 10px; margin-left: 481px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">Rust</div></div></div></foreignObject><text x="540" y="14" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">Rust</text></switch></g><rect x="480" y="30" width="120" height="20" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 40px; margin-left: 481px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">C++</div></div></div></foreignObject><text x="540" y="44" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">C++</text></switch></g><path d="M 230 220 L 270 260 L 230 300 L 190 260 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 78px; height: 1px; padding-top: 260px; margin-left: 191px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">IPC parent?</div></div></div></foreignObject><text x="230" y="264" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">IPC parent?</text></switch></g><path d="M 230 300 L 230 433.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 230 438.88 L 226.5 431.88 L 230 433.63 L 233.5 431.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 140 260 L 183.63 260" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 188.88 260 L 181.88 263.5 L 183.63 260 L 181.88 256.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="230" y="310" width="40" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 320px; margin-left: 231px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">YES</div></div></div></foreignObject><text x="250" y="324" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">YES</text></switch></g><rect x="270" y="240" width="40" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 250px; margin-left: 271px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">NO</div></div></div></foreignObject><text x="290" y="254" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">NO</text></switch></g><rect x="360" y="230" width="230" height="200" fill="#f5f5f5" stroke="#666666" pointer-events="all"/><path d="M 270 260 L 460 260 Q 470 260 470 261.82 L 470 263.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 470 268.88 L 466.5 261.88 L 470 263.63 L 473.5 261.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><path d="M 470 290 L 470 310 L 470 290 L 470 303.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 470 308.88 L 466.5 301.88 L 470 303.63 L 473.5 301.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="410" y="270" width="120" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 280px; margin-left: 411px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">transfer encoding</div></div></div></foreignObject><text x="470" y="284" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">transfer encoding</text></switch></g><path d="M 470 330 L 470 353.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 470 358.88 L 466.5 351.88 L 470 353.63 L 473.5 351.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="410" y="310" width="120" height="20" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 320px; margin-left: 411px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">IPC send</div></div></div></foreignObject><text x="470" y="324" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">IPC send</text></switch></g><path d="M 470 380 L 470 393.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 470 398.88 L 466.5 391.88 L 470 393.63 L 473.5 391.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="410" y="360" width="120" height="20" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 370px; margin-left: 411px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">IPC recv</div></div></div></foreignObject><text x="470" y="374" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">IPC recv</text></switch></g><path d="M 470 420 L 470 470 L 296.37 470" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/><path d="M 291.12 470 L 298.12 466.5 L 296.37 470 L 298.12 473.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/><rect x="410" y="400" width="120" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 410px; margin-left: 411px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">transfer decoding</div></div></div></foreignObject><text x="470" y="414" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">transfer decoding</text></switch></g><rect x="520" y="230" width="70" height="20" fill="none" stroke="none" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 240px; margin-left: 521px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">IPC layer</div></div></div></foreignObject><text x="555" y="244" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">IPC layer</text></switch></g><rect x="100" y="210" width="40" height="20" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(-0.5 -0.5)"><switch><foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 38px; height: 1px; padding-top: 220px; margin-left: 101px;"><div style="box-sizing: border-box; font-size: 0; text-align: center; "><div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "><font style="font-size: 11px">C API</font></div></div></div></foreignObject><text x="120" y="224" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">C API</text></switch></g></g><switch><g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/><a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank"><text text-anchor="middle" font-size="10px" x="50%" y="100%">Viewer does not support full SVG 1.1</text></a></switch></svg> \ No newline at end of file
diff --git a/toolkit/components/glean/docs/index.md b/toolkit/components/glean/docs/index.md
new file mode 100644
index 0000000000..ad43fa6fee
--- /dev/null
+++ b/toolkit/components/glean/docs/index.md
@@ -0,0 +1,32 @@
+# Firefox on Glean (FOG)
+
+Firefox on Glean (FOG) is the name of the layer that integrates the
+[Glean SDK][glean-sdk] into
+[Firefox Desktop](https://www.firefox.com/).
+It is currently being designed and implemented.
+
+The [Glean SDK][glean-sdk]
+is a data collection library built by Mozilla for use in its products.
+Like [Telemetry][telemetry], it can be used to
+(in accordance with our [Privacy Policy][privacy-policy])
+send anonymous usage statistics to Mozilla in order to make better decisions.
+
+If you have any questions, please reach out to the team on
+[#glean:mozilla.org][glean-matrix].
+
+```eval_rst
+.. toctree::
+ :titlesonly:
+ :maxdepth: 1
+ :glob:
+
+ Glean SDK Source <https://github.com/mozilla/glean/>
+ Glean SDK Documentation <https://mozilla.github.io/glean/book/index.html>
+ *
+
+```
+[telemetry]: ../telemetry
+[glean-sdk]: https://github.com/mozilla/glean/
+[book-of-glean]: https://mozilla.github.io/glean/book/index.html
+[privacy-policy]: https://www.mozilla.org/privacy/
+[glean-matrix]: https://chat.mozilla.org/#/room/#glean:mozilla.org
diff --git a/toolkit/components/glean/docs/ipc.md b/toolkit/components/glean/docs/ipc.md
new file mode 100644
index 0000000000..2b17b6b982
--- /dev/null
+++ b/toolkit/components/glean/docs/ipc.md
@@ -0,0 +1,106 @@
+# Inter-process Communication (IPC)
+
+Firefox Desktop is a multi-process desktop application.
+Code requiring instrumentation may be on any of its processes,
+so FOG needs to provide facilities to enable that.
+
+## Design
+
+The IPC Design of FOG was worked out in
+[bug 1618253](https://bugzilla.mozilla.org/show_bug.cgi?id=1618253).
+
+It centred around a few specific concepts:
+
+### Forbidding Non-Commutative Operations
+
+Because we cannot nicely impose a canonical ordering of metric operations across all processes,
+FOG forbids non-[commutative](https://en.wikipedia.org/wiki/Commutative_property)
+metric operations in some circumstances.
+
+For example,
+`Add()`-ing to a Counter metric works from multiple processes because the order doesn't matter.
+However, given a String metric being `Set()` from multiple processes simultaneously,
+which value should it take?
+
+This ambiguity is not a good foundation to build trust on,
+so we forbid setting a String metric from multiple processes.
+
+#### List of Forbidden Operations
+
+* Boolean's `set` (this is the metric type's only operation)
+* Labeled Boolean's `set` (this is the metric type's only operation)
+* String's `set` (this is the metric type's only operation)
+* Labeled String's `set` (this is the metric type's only operation)
+* String List's `set`
+ * `add` is permitted (order and uniqueness are not guaranteed)
+* Timespan's `start`, `stop`, and `cancel` (these are the metric type's only operations)
+* UUID's `set` and `generateAndSet` (these are the metric type's only operations)
+* Datetime's `set` (this is the metric type's only operation)
+* Quantity's `set` (this is the metric type's only operation)
+
+This list may grow over time as new metric types are added.
+If there's an operation/metric type on this list that you need to use in a non-parent process,
+please reach out
+[on the #glean channel](https://chat.mozilla.org/#/room/#glean:mozilla.org)
+and we'll help you out.
+
+### Process Agnosticism
+
+For metric types that can be used cross-process,
+FOG provides no facility for identifying which process the instrumentation is on.
+
+What this means is that if you accumulate to a
+[Timing Distribution](https://mozilla.github.io/glean/book/user/metrics/timing_distribution.html)
+in multiple processes,
+all the samples from all the processes will be combined in the same metric.
+
+If you wish to distinguish samples from different process types,
+you will need multiple metrics and inline code to select the proper one for the given process.
+For example:
+
+```C++
+if (XRE_GetProcessType() == GeckoProcessType_Default) {
+ mozilla::glean::performance::cache_size.Accumulate(numBytes / 1024);
+} else {
+ mozilla::glean::performance::non_main_process_cache_size.Accumulate(numBytes / 1024);
+}
+```
+
+### Scheduling
+
+FOG makes no guarantee about when non-main-process metric values are sent across IPC.
+FOG will try its best to schedule opportunistically in idle moments.
+
+There are a few cases where we provide more firm guarantees:
+
+#### Tests
+
+There are test-only APIs in Rust, C++,
+and Javascript that expose when a complete flush of child process metric values has completed.
+See [the test documentation](testing.md) for more details.
+
+#### Built-in Pings
+
+[Built-in pings](https://mozilla.github.io/glean/book/user/pings/index.html)
+will send only after all metric values from all child processes have been collected.
+
+We cannot at this time provide the same guarantee for
+[Custom Pings](https://mozilla.github.io/glean/book/user/pings/custom.html).
+
+#### Shutdown
+
+We will make a best effort during an orderly shutdown to flush all pending data in child processes.
+
+### Mechanics
+
+At present
+(see [bug 1641989](https://bugzilla.mozilla.org/show_bug.cgi?id=1641989))
+FOG uses messages on the PContent protocol.
+This enables communication between content child processes and the parent process.
+
+The rough design is that the Parent can request an immediate flush of pending data,
+and each Child can decide to flush its pending data whenever it wishes.
+
+Pending Data is a buffer of bytes generated by `bincode` in Rust in the Child,
+handed off to C++, passed over IPC,
+then given back to `bincode` in Rust on the Parent.
diff --git a/toolkit/components/glean/docs/new_definitions_file.md b/toolkit/components/glean/docs/new_definitions_file.md
new file mode 100644
index 0000000000..1e5b188caa
--- /dev/null
+++ b/toolkit/components/glean/docs/new_definitions_file.md
@@ -0,0 +1,85 @@
+# New Metrics and Pings
+
+**Note:** FOG is not ready to be used,
+so you probably should not be adding metrics and pings using this guide yet.
+Instead, please use Firefox Telemetry unless you have explicitly been given permission by a
+[Telemetry Module Peer](https://wiki.mozilla.org/Modules/All#Telemetry).
+
+To add a new metric or ping to Firefox Desktop you should follow the
+[Glean SDK documentation on the subject](https://mozilla.github.io/glean/book/user/adding-new-metrics.html),
+with some few twists.
+
+## IPC
+
+Firefox Desktop is made of multiple processes.
+You can record data from any process in Firefox Desktop
+[subject to certain conditions](ipc.md).
+
+If you will be recording data to this metric in multiple processes,
+you should make yourself aware of those conditions.
+
+## Where do I Define new Metrics and Pings?
+
+Metrics and pings are defined in their definitions files
+(`metrics.yaml` or `pings.yaml`, respectively).
+But where can you find `metrics.yaml` or `pings.yaml`?
+
+If you're not the first person in your component to ask that question,
+the answer is likely "in the root of your component".
+Look for the definitions files near to where you are instrumenting your code.
+Or you can look in
+`toolkit/components/glean/metrics_index.py`
+to see the list of all currently-known definitions files.
+
+If you _are_ the first person in your component to ask that question,
+you get to choose where to start them!
+We recommend adding them in the root of your component, next to a `moz.build`.
+
+When you do so, be sure to edit `toolkit/components/glean/metrics_index.py`,
+adding your definitions files to the Python lists therein.
+If you don't, no API will be generated for your metrics and your build will fail.
+
+In addition, do not forget to file a bug in `Data Platform and Tools :: General`
+asking for your definitions files to be added to the others for `firefox.desktop`.
+If you don't, your metrics will not show up in datasets and tools
+because the pipeline won't know that they exist.
+
+If you have any questions, be sure to ask on
+[the #glean channel](https://chat.mozilla.org/#/room/#glean:mozilla.org).
+
+**Note:** Do _not_ use `toolkit/components/glean/metrics.yaml`
+or `toolkit/components/glean/pings.yaml`.
+These are for metrics instrumenting the code under `toolkit/components/glean`
+and are not general-purpose locations for adding metrics and pings.
+
+## How does Expiry Work?
+
+In FOG,
+unlike in other Glean-SDK-using projects,
+metrics expire based on Firefox application version.
+This is to allow metrics to be valid over the entire life of an application version,
+whether that is the 4-6 weeks of usual releases or the 13 months of ESR releases.
+
+There are three values accepted in the `expires` field of `metrics.yaml`s for FOG:
+* `"X"` (where `X` is the major portion of a Firefox Desktop version) -
+ The metric will be expired when the `MOZ_APP_VERSION` reaches or exceeds `X`.
+ (For example, when the Firefox Version is `88.0a1`,
+ all metrics marked with `expires: "88"` or lower will be expired.)
+ This is the recommended form for all new metrics to ensure they stop recording when they stop being relevant.
+* `expired` - For marking a metric as manually expired.
+ Not usually used, but sometimes helpful for internal tests.
+* `never` - For marking a metric as part of a permanent data collection.
+ Metrics marked with `never` must have [instrumentation tests](testing.md).
+
+For more information on what expiry means and the
+`metrics.yaml` format, see
+[the Glean SDK docs](https://mozilla.github.io/glean/book/user/metric-parameters.html)
+on this subject. Some quick facts:
+
+* Data collected to expired metrics is not recorded or sent.
+* Recording to expired metrics is not an error at runtime.
+* Expired metrics being in a `metrics.yaml` is a linting error in `glean_parser`.
+* Expired (and non-expired) metrics that are no longer useful should be promptly removed from your `metrics.yaml`.
+ This reduces the size and improves the performance of Firefox
+ (and speeds up the Firefox build process)
+ by decreasing the amount of code that needs to be generated.
diff --git a/toolkit/components/glean/docs/new_metric_types.md b/toolkit/components/glean/docs/new_metric_types.md
new file mode 100644
index 0000000000..c950978f95
--- /dev/null
+++ b/toolkit/components/glean/docs/new_metric_types.md
@@ -0,0 +1,114 @@
+# Adding a New Metric Type
+
+This document covers how to add a new metric type to FOG.
+You should only have to do this if a new metric type is added to the
+[Glean SDK](https://mozilla.github.io/glean/book/user/metrics/index.html)
+and it is needed in Firefox Desktop.
+
+## IPC
+
+For detailed information about the IPC design,
+including a list of forbidden operations,
+please consult
+[the FOG IPC documentation](ipc.md).
+
+When adding a new metric type, the main IPC considerations are:
+* Which operations are forbidden because they are not commutative?
+ * Most `set`-style operations cannot be reconciled sensibly across multiple processes.
+* If there are non-forbidden operations,
+what partial representation will this metric have in non-main processes?
+Put another way, what shape of storage will this take up in the
+[IPC Payload](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/api/src/ipc.rs)?
+ * For example, Counters can aggregate all partial counts together to a single
+ "partial sum". So
+ [its representation](https://searchfox.org/mozilla-central/rev/803b368879fa332e8e2c1840bf1ec164f7ed2c32/toolkit/components/glean/api/src/ipc.rs#45)
+ in the IPC Payload is just a single number per Counter.
+ * In contrast, Timing Distributions' durations are calculated in the core,
+ so it can't calculate samples in child processes.
+ Instead we will need to record the start and stop instants as pairs,
+ more or less sending a command stream with timing information.
+
+To implement IPC support in a metric type,
+we split the metric into three pieces:
+1. An umbrella `enum` with the name `MetricTypeMetric`.
+ * It has a `Child` and a `Parent` variant.
+ * It is IPC-aware and is responsible for
+ * If on a non-parent-process,
+ either storing partial representations in the IPC Payload,
+ or logging errors if forbidden non-test APIs are called.
+ (Or panicking if test APIs are called.)
+ * If on the parent process, dispatching API calls to the core.
+2. The parent-process implementation is supplied by
+ [the RLB](https://crates.io/crates/glean/).
+ * For testing, it stores the `MetricId` that identifies this particular metric in a cross-process fashion.
+ * For testing, it exposes a `child_metric()` function to create its `Child` equivalent.
+ * For testing and if it supports operations in a non-parent-process, it exposes a `metric_id()` function to access the stored `MetricId`.
+3. The `MetricTypeIpc` is the ipc-aware non-parent-process implementation.
+ * If it does support operations in non-parent processes it stores the `MetricId` that identifies this particular metric in a cross-process fashion.
+
+## Rust
+
+FOG uses the Rust Language Binding APIs (the `glean` crate) with a layer of IPC on top.
+
+The IPC additions and glean-core trait implementations are in the
+[`private` module of the `fog` crate](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/api/src/metrics).
+
+Each metric type gets its own file, mimicking the structure in
+[`glean_core`](https://github.com/mozilla/glean/tree/main/glean-core/src/metrics)
+and [`glean`](https://github.com/mozilla/glean/tree/main/glean-core/rlb/src/private).
+
+Every method on the metric type is public for now,
+including test methods.
+
+## C++ and JS
+
+The C++ and JS APIs are implemented [atop the Rust API](code_organization.md).
+We treat them both together since, though they're different languages,
+they're both implemented in C++ and share much of their implementation.
+
+Each metric type has six pieces you'll need to cover:
+
+### 1. MLA FFI
+
+- Using our convenient macros, define the Multi-Language Architecture's FFI layer above the Rust API in [`api/src/ffi/mod.rs`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/api/src/ffi/mod.rs).
+
+### 2. C++ Impl
+
+- Implement a type called `XMetric` (e.g. `CounterMetric`) in `mozilla::glean::impl` in [`bindings/private/`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/bindings/private/).
+ - Its methods should be named the same as the ones in the Rust API, transformed to `CamelCase`.
+ - They should all be public.
+ - Multiplex the FFI's `test_have` and `test_get` functions into a single `TestGetValue` function that returns a `mozilla::Maybe` wrapping the C++ type that best fits the metric type.
+- Include the new metric type in
+ [`bindings/MetricTypes.h`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/bindings/MetricTypes.h)
+ and
+ [`build_scripts/glean_parser_ext/util.py`'s `IMPLEMENTED_CPP_TYPES`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/build_scripts/glean_parser_ext/util.py).
+- Include the new files in [`moz.build`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/bindings/MetricTypes.h). The header file should be added to `EXPORTS.mozilla.glean.bindings` and the `.cpp` file should be added to `UNIFIED_SOURCES`.
+
+### 3. IDL
+
+- Duplicate the public API (including its docs) to [`xpcom/nsIGleanMetrics.idl`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/xpcom/nsIGleanMetrics.idl) with the name `nsIGleanX` (e.g. `nsIGleanCounter`).
+ - Inherit from `nsISupports`.
+ - The naming style for members here is `lowerCamelCase`. You'll need a [GUID](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Generating_GUIDs) because this is XPCOM, but you'll only need the canonical form since we're only exposing to JS.
+ - The `testGetValue` method will return a `jsval`.
+
+### 4. JS Impl
+
+- Add an `nsIGleanX`-deriving, `XMetric`-owning type called `GleanX` (e.g. `GleanCounter`) in the same header and `.cpp` as `XMetric` in [`bindings/private/`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/bindings/private/).
+ - Don't declare any methods beyond a ctor (takes a `uint32_t` metric id, init-constructs a `impl::XMetric` member) and dtor (`default`): the IDL will do the rest so long as you remember to add `NS_DECL_ISUPPORTS` and `NS_DECL_NSIGLEANX`.
+ - In the definition of `GleanX`, member identifiers are back to `CamelCase` and need macros like `NS_IMETHODIMP`. Delegate operations to the owned `XMetric`, returning `NS_OK` no matter what in non-test methods.
+ - Test-only methods can return `NS_ERROR` codes on failures, but mostly return `NS_OK` and use `undefined` in the `JS::MutableHandleValue` result to signal no value.
+
+### 6. Tests
+
+Two languages means two test suites.
+
+- Add a never-expiring test-only metric of your type to [`test_metrics.yaml`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/test_metrics.yaml).
+ - Feel free to be clever with the name, but be sure to make clear that it is test-only.
+- **C++ Tests (GTest)** - Add a small test case to [`gtest/TestFog.cpp`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/gtest/TestFog.cpp).
+ - For more details, peruse the [testing docs](testing.md).
+- **JS Tests (xpcshell)** - Add a small test case to [`xpcshell/test_Glean.js`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/xpcshell/test_Glean.js).
+ - For more details, peruse the [testing docs](testing.md).
+
+### 7. API Documentation
+
+TODO
diff --git a/toolkit/components/glean/docs/preferences.md b/toolkit/components/glean/docs/preferences.md
new file mode 100644
index 0000000000..1a0efffc10
--- /dev/null
+++ b/toolkit/components/glean/docs/preferences.md
@@ -0,0 +1,21 @@
+# Preferences
+
+## User Preferences
+
+`datareporting.healthreport.uploadEnabled`
+
+This determines whether the Glean SDK is enabled.
+It can be controlled by users via `about:preferences#privacy`.
+If this is set to false from true, we send a
+["deletion-request" ping](https://mozilla.github.io/glean/book/user/pings/deletion_request.html)
+and no data collections will be persisted or reported from that point.
+
+## Test-only Preferences
+
+`telemetry.fog.test.localhost_port`
+
+If set to a value `port` which is greater than 0, pings will be sent to
+`http://localhost:port` instead of `https://incoming.telemetry.mozilla.org`.
+If set to a value which is less than 0, FOG will not squelch all sent pings,
+telling the Glean SDK that the ping was sent successfully.
+Defaults to 0.
diff --git a/toolkit/components/glean/docs/storage.md b/toolkit/components/glean/docs/storage.md
new file mode 100644
index 0000000000..00464c6173
--- /dev/null
+++ b/toolkit/components/glean/docs/storage.md
@@ -0,0 +1,14 @@
+# Storage
+
+Both FOG and the Glean SDK require some storage in the
+[Firefox Profile Directory](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Multiple_profiles).
+
+## FOG
+
+At present FOG's storage is limited to its [preferences](preferences.md).
+
+## Glean SDK
+
+The Glean SDK stores things in the
+[Glean Data Directory](https://mozilla.github.io/glean/book/dev/core/internal/directory-structure.html)
+which can be found at `<profile_dir>/datareporting/glean`.
diff --git a/toolkit/components/glean/docs/style_guide.md b/toolkit/components/glean/docs/style_guide.md
new file mode 100644
index 0000000000..6f37f23a8a
--- /dev/null
+++ b/toolkit/components/glean/docs/style_guide.md
@@ -0,0 +1,58 @@
+# FOG Documentation Style Guide
+
+FOG's Documentation is written in Markdown.
+You can find its source at `toolkit/components/glean/docs`.
+
+## Line breaks
+
+We will use [semantic linefeeds]:
+* Break anywhere before 80-100 characters
+* Break after any punctuation when it makes sense
+* Break before or after any markdown when it makes sense
+
+**Tip:** To keep lines narrow, use markdown's [reference link]
+feature whenever it makes sense (or all the time. Up to you.).
+
+## Linking to other documentation
+
+Linking to other external documentation is [easy][reference link].
+Linking to other pieces of documentation in the source docs might not be.
+
+To link to another markdown page in FOG's documentation, you can use
+```md
+[link text](page_name.md)
+```
+
+Sphinx will automagically transform that to an
+appropriately-base-url'd url with a `.html` suffix.
+
+Unfortunately, this doesn't work for linking to
+`.rst` files like those in use in [Telemetry]'s documentation.
+(Follow [bug 1621950] for updates).
+
+In those cases you have to link it like it's html.
+For example, to link to [Telemetry] you can use either of
+```md
+[Telemetry](../telemetry)
+[Telemetry](../telemetry/index.html)
+```
+
+Both will work. Both will generate warnings.
+For example, the first form will generate this:
+```console
+None:any reference target not found: ../telemetry
+```
+But it will still work because linking to a directory in html links to its
+`index.html` (which is where `index.rst` ends up).
+
+We can suppress this by putting a fake anchor
+(like `#https://`) on the end to fool Sphinx into not checking it.
+But that seems like a hack more unseemly than the warnings,
+so let's not.
+
+
+[semantic linefeeds]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/
+[reference link]: https://spec.commonmark.org/0.29/#reference-link
+[Telemetry]: ../telemetry
+[#firefox-source-docs:mozilla.org]: https://chat.mozilla.org/#/room/#firefox-source-docs:mozilla.org
+[bug 1621950]: https://bugzilla.mozilla.org/show_bug.cgi?id=1621950
diff --git a/toolkit/components/glean/docs/testing.md b/toolkit/components/glean/docs/testing.md
new file mode 100644
index 0000000000..2ef6ace0f6
--- /dev/null
+++ b/toolkit/components/glean/docs/testing.md
@@ -0,0 +1,117 @@
+# Testing
+
+Given the multiple API languages, processes, and dependencies,
+testing FOG is a matter of choosing the right tool for the situation.
+
+## Logging
+
+An often-overlooked first line of testing is "what do the logs say?".
+To turn on logging for FOG, use any of the following:
+* Run Firefox with `RUST_LOG="fog_control,fog,glean_core"`.
+ * On some platforms this will use terminal colours to indicate log level.
+* Run Firefox with `MOZ_LOG="timestamp,glean::*:5,fog::*:5,fog_control::*:5,glean_core::*:5"`.
+* Set the following prefs:
+ * `logging.config.timestamp` to `true`
+ * `logging.fog_control::*` to `5`
+ * `logging.fog::*` to `5`
+ * `logging.glean_core::*` to `5`
+
+For more information on logging in Firefox Desktop, see the
+[Gecko Logging docs](https://developer.mozilla.org/docs/Mozilla/Developer_guide/Gecko_Logging).
+
+## `about:glean`
+
+`about:glean` is a page in a running Firefox that allows you to
+[debug the Glean SDK](https://mozilla.github.io/glean/book/user/debugging/index.html)
+in Firefox Desktop.
+It does this through the displayed user interface (just follow the instructions).
+
+## Rust
+
+Not all of our Rust code can be tested in a single fashion, unfortunately.
+
+### Using `rusttests`
+
+If the crate you're testing has no Gecko symbols you can write standard
+[Rust tests](https://doc.rust-lang.org/book/ch11-01-writing-tests.html).
+
+This supports both unit tests
+(inline in the file under test) and integration tests
+(in the `tests/` folder in the crate root).
+Metric type tests are currently written as unit tests inline in the file,
+as they require access to the metric ID, which should only be exposed in tests.
+
+To run FOG's `rusttests` suite use `mach rusttests`
+
+If the crate uses only a few Gecko symbols, they may use the
+`with_gecko` feature to conditionally use them.
+This allows the crate to test its non-Gecko-adjacent code using Rust tests.
+(You will need to cover the Gecko-adjacent code via another means.)
+
+### Using `gtest`
+
+Because Gecko symbols aren't built for the
+`rusttests` build,
+any test that is written for code that uses Gecko symbols should be written as a
+[`gtest`](https://github.com/google/googletest)
+in `toolkit/components/glean/gtest/`.
+You can write the actual test code in Rust.
+It needs to be accompanied by a C++ GTest that calls a C FFI-exported Rust function.
+See [Testing & Debugging Rust Code](/testing-rust-code/) for more.
+See [`toolkit/components/glean/gtest/TestFog.cpp`](https://searchfox.org/mozilla-central/source/toolkit/components/glean/gtest/TestFog.cpp)
+and [`toolkit/components/glean/gtest/test.rs`](https://searchfox.org/mozilla-central/source/toolkit/components/glean/gtest/test.rs)
+for an example.
+
+By necessity these can only be integration tests against the compiled crate.
+
+**Note:** When adding a new test file, don't forget to add it to
+`toolkit/components/glean/gtest/moz.build` and use the
+`FOG` prefix in your test names
+(e.g. `TEST(FOG, YourTestName) { ... }`).
+
+To run FOG's Rust `gtest` suite use `mach gtest FOG.*`
+
+## Python
+
+The [Glean Parser](https://github.com/mozilla/glean_parser/)
+has been augmented to generate FOG-specific APIs for Glean metrics.
+This augmentation is tested by running:
+
+`mach test toolkit/components/glean/pytest`
+
+These tests require Python 3+.
+If your default Python is Python 2, you may need to instead run:
+
+`python3 mach python-test toolkit/components/glean/pytest`
+
+## C++
+
+To test the C++ parts of FOG's implementation
+(like metric types)
+you should use `gtest`.
+FOG's `gtest` tests are in
+[`gtest/`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/gtest/).
+
+You can either add a test case to an existing file or add a new file.
+If you add a new file, remember to add it to the
+[`moz.build`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/gtest/moz.build))
+or the test runner won't be able to find it.
+
+All tests should start with `FOG` so that all tests are run with
+`./mach gtest FOG*`.
+
+## JS
+
+To test the JS parts of FOG's implementation
+(like metric types)
+you should use `xpcshell`.
+FOG's `xpcshell` tests are in
+[`xpcshell/`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/xpcshell).
+
+You can either add a test case to an existing file or add a new file.
+If you add a new file, remember to add it to the
+[`xpcshell.ini`](https://hg.mozilla.org/mozilla-central/file/tip/toolkit/components/glean/xpcshell/xpcshell.ini)
+or the test runner will not be able to find it.
+
+To run FOG's JS tests, run:
+`./mach test toolkit/components/glean/xpcshell`
diff --git a/toolkit/components/glean/docs/updating_parser.md b/toolkit/components/glean/docs/updating_parser.md
new file mode 100644
index 0000000000..6603090279
--- /dev/null
+++ b/toolkit/components/glean/docs/updating_parser.md
@@ -0,0 +1,37 @@
+# Updating glean_parser
+
+Project FOG uses the `glean_parser` to generate code from metric definitions.
+It depends on [glean-parser] from pypi.org
+
+[glean-parser]: https://pypi.org/project/glean-parser/
+
+To update the in-tree glean-parser run
+
+```
+./mach vendor python glean_parser==M.m.p
+```
+
+where `M.m.p` is the version number, e.g. 1.28.0.
+
+```eval_rst
+.. note::
+
+ **Important**: the glean_parser and all of its dependencies must support Python 3.5, as discussed here.
+ This is the minimum version supported by mach and installed on the CI images for running tests.
+ This is enforced by the version ranges declared in the Python installation manifest.
+```
+
+## Version mismatch of the Python dependencies
+
+The logic for handling version mismatches is very similar to the one for the Rust crates.
+See [Updating the Glean SDK](updating_sdk.html) for details.
+However, updating Python packages also requires to think about Python 3.5 (and Python 2, still) compatibility.
+
+## Keeping versions in sync
+
+The Glean SDK and `glean_parser` are currently released as separate projects.
+However each Glean SDK release requires a specific `glean_parser` version.
+When updating one or the other ensure versions stay compatible.
+You can find the currently used `glean_parser` version in the Glean SDK source tree, e.g. in [sdk_generator.sh].
+
+[sdk_generator.sh]: https://github.com/mozilla/glean/blob/main/glean-core/ios/sdk_generator.sh#L28
diff --git a/toolkit/components/glean/docs/updating_sdk.md b/toolkit/components/glean/docs/updating_sdk.md
new file mode 100644
index 0000000000..70752c980b
--- /dev/null
+++ b/toolkit/components/glean/docs/updating_sdk.md
@@ -0,0 +1,39 @@
+# Updating the Glean SDK
+
+Project FOG uses the published Glean SDK.
+It currently depends on [glean-core] from crates.io.
+
+[glean-core]: https://crates.io/crates/glean-core
+
+To update the dependency:
+
+1. Bump the version of the `glean-core` crate in and `toolkit/components/glean/Cargo.toml` and `toolkit/components/glean/api/Cargo.toml`.
+2. Run `mach vendor rust`. This fetches all dependencies and adds them to `mozilla-central/third_pary/rust`.
+
+## Version mismatches of Rust dependencies
+
+Other crates that are already vendored might require a different version of the same dependencies that the Glean SDK requires.
+The general strategy for Rust dependencies is to keep one single version of the dependency in-tree
+(see [comment #8 in bug 1591555](https://bugzilla.mozilla.org/show_bug.cgi?id=1591555#c8)).
+This might be hard to do in reality since some dependencies might require tweaks in order to work.
+The following strategy can be followed to decide on version mismatches:
+
+* If the versions only **differ by the patch version**, Cargo will keep the vendored version,
+ unless some other dependency pinned specific patch versions;
+ assuming it doesn’t break the Glean SDK;
+ if it does, follow the next steps;
+* If the version of the **vendored dependency is newer** (greater major or minor version) than the version required by the Glean SDK,
+ [file a bug in the Glean SDK component][glean-bug] to get Glean to require the same version;
+* If the version of the **vendored dependency is older** (lower major or minor version), consider updating the vendored version to the newer one;
+ seek review from the person who vendored that dependency in the first place;
+ if that is not possible or breaks mozilla-central build, then consider keeping both versions vendored in-tree; please note that this option will probably only be approved for small crates.
+
+## Keeping versions in sync
+
+The Glean SDK and `glean_parser` are currently released as separate projects.
+However each Glean SDK release requires a specific `glean_parser` version.
+When updating one or the other ensure versions stay compatible.
+You can find the currently used `glean_parser` version in the Glean SDK source tree, e.g. in [sdk_generator.sh].
+
+[sdk_generator.sh]: https://github.com/mozilla/glean/blob/main/glean-core/ios/sdk_generator.sh#L28
+[glean-bug]: https://bugzilla.mozilla.org/enter_bug.cgi?product=Data+Platform+and+Tools&component=Glean%3A+SDK&priority=P3&status_whiteboard=%5Btelemetry%3Aglean-rs%3Am%3F%5D
diff --git a/toolkit/components/glean/gtest/Cargo.toml b/toolkit/components/glean/gtest/Cargo.toml
new file mode 100644
index 0000000000..83bfd3d244
--- /dev/null
+++ b/toolkit/components/glean/gtest/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "fog-gtest"
+version = "0.1.0"
+authors = ["glean-team@mozilla.com"]
+license = "MPL-2.0"
+description = "Tests for the FOG crate"
+
+[dependencies]
+fog = { path = "../api" }
+
+[lib]
+path = "test.rs"
diff --git a/toolkit/components/glean/gtest/TestFog.cpp b/toolkit/components/glean/gtest/TestFog.cpp
new file mode 100644
index 0000000000..16add5b973
--- /dev/null
+++ b/toolkit/components/glean/gtest/TestFog.cpp
@@ -0,0 +1,220 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/glean/GleanPings.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Tuple.h"
+#include "nsTArray.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "nsString.h"
+#include "prtime.h"
+
+using mozilla::Preferences;
+using namespace mozilla::glean;
+using namespace mozilla::glean::impl;
+
+#define DATA_PREF "datareporting.healthreport.uploadEnabled"
+
+extern "C" {
+// This function is called by the rust code in test.rs if a non-fatal test
+// failure occurs.
+void GTest_FOG_ExpectFailure(const char* aMessage) {
+ EXPECT_STREQ(aMessage, "");
+}
+}
+
+// Initialize FOG exactly once.
+// This needs to be the first test to run!
+TEST(FOG, FogInitDoesntCrash)
+{
+ Preferences::SetInt("telemetry.fog.test.localhost_port", -1);
+ ASSERT_EQ(NS_OK, fog_init());
+ // Fog init isn't actually done (it passes work to a background thread)
+ Preferences::SetBool(DATA_PREF, false);
+ Preferences::SetBool(DATA_PREF, true);
+}
+
+// TODO: to be enabled after changes from bug 1677455 are vendored.
+// extern "C" void Rust_MeasureInitializeTime();
+// TEST(FOG, TestMeasureInitializeTime)
+// { Rust_MeasureInitializeTime(); }
+
+TEST(FOG, BuiltinPingsRegistered)
+{
+ Preferences::SetInt("telemetry.fog.test.localhost_port", -1);
+ nsAutoCString metricsPingName("metrics");
+ nsAutoCString baselinePingName("baseline");
+ nsAutoCString eventsPingName("events");
+ ASSERT_EQ(NS_OK, fog_submit_ping(&metricsPingName));
+ ASSERT_EQ(NS_OK, fog_submit_ping(&baselinePingName));
+ ASSERT_EQ(NS_OK, fog_submit_ping(&eventsPingName));
+}
+
+TEST(FOG, TestCppCounterWorks)
+{
+ mozilla::glean::test_only::bad_code.Add(42);
+
+ ASSERT_EQ(
+ 42,
+ mozilla::glean::test_only::bad_code.TestGetValue("test-ping"_ns).value());
+ // And test that the ping name's optional, while you're at it:
+ ASSERT_EQ(42, test_only::bad_code.TestGetValue().value());
+}
+
+TEST(FOG, TestCppStringWorks)
+{
+ auto kValue = "cheez!"_ns;
+ mozilla::glean::test_only::cheesy_string.Set(kValue);
+
+ ASSERT_STREQ(kValue.get(), mozilla::glean::test_only::cheesy_string
+ .TestGetValue("test-ping"_ns)
+ .value()
+ .get());
+}
+
+TEST(FOG, TestCppTimespanWorks)
+{
+ mozilla::glean::test_only::can_we_time_it.Start();
+ PR_Sleep(PR_MillisecondsToInterval(10));
+ mozilla::glean::test_only::can_we_time_it.Stop();
+
+ ASSERT_TRUE(
+ mozilla::glean::test_only::can_we_time_it.TestGetValue("test-ping"_ns)
+ .value() > 0);
+}
+
+TEST(FOG, TestCppUuidWorks)
+{
+ nsCString kTestUuid("decafdec-afde-cafd-ecaf-decafdecafde");
+ test_only::what_id_it.Set(kTestUuid);
+ ASSERT_STREQ(
+ kTestUuid.get(),
+ test_only::what_id_it.TestGetValue("test-ping"_ns).value().get());
+
+ test_only::what_id_it.GenerateAndSet();
+ // Since we generate v4 UUIDs, and the first character of the third group
+ // isn't 4, this won't ever collide with kTestUuid.
+ ASSERT_STRNE(
+ kTestUuid.get(),
+ test_only::what_id_it.TestGetValue("test-ping"_ns).value().get());
+}
+
+TEST(FOG, TestCppBooleanWorks)
+{
+ mozilla::glean::test_only::can_we_flag_it.Set(false);
+
+ ASSERT_EQ(false, mozilla::glean::test_only::can_we_flag_it
+ .TestGetValue("test-ping"_ns)
+ .value());
+}
+
+// TODO: to be enabled after changes from bug 1677448 are vendored.
+// TEST(FOG, TestCppDatetimeWorks)
+// {
+// PRExplodedTime date = {0, 35, 10, 12, 6, 10, 2020, 0, 0, {5 * 60 * 60, 0}};
+// test_only::what_a_date.Set(&date);
+//
+// auto received = test_only::what_a_date.TestGetValue("test-ping");
+// ASSERT_STREQ(received.value().get(), "2020-11-06T12:10:35+05:00");
+// }
+
+using mozilla::MakeTuple;
+using mozilla::Tuple;
+using mozilla::glean::test_only_ipc::AnEventKeys;
+
+TEST(FOG, TestCppEventWorks)
+{
+ test_only_ipc::no_extra_event.Record();
+ ASSERT_TRUE(test_only_ipc::no_extra_event.TestGetValue("store1"_ns).isSome());
+
+ // Ugh, this API...
+ nsTArray<Tuple<test_only_ipc::AnEventKeys, nsCString>> extra;
+ nsCString val = "can set extras"_ns;
+ extra.AppendElement(MakeTuple(AnEventKeys::Extra1, val));
+
+ test_only_ipc::an_event.Record(std::move(extra));
+ ASSERT_TRUE(test_only_ipc::an_event.TestGetValue("store1"_ns).isSome());
+}
+
+TEST(FOG, TestCppMemoryDistWorks)
+{
+ test_only::do_you_remember.Accumulate(7);
+ test_only::do_you_remember.Accumulate(17);
+
+ DistributionData data =
+ test_only::do_you_remember.TestGetValue("test-ping"_ns).ref();
+ // Sum is in bytes, test_only::do_you_remember is in megabytes. So
+ // multiplication ahoy!
+ ASSERT_EQ(data.sum, 24UL * 1024 * 1024);
+ for (auto iter = data.values.Iter(); !iter.Done(); iter.Next()) {
+ const uint64_t bucket = iter.Key();
+ const uint64_t count = iter.UserData();
+ ASSERT_TRUE(count == 0 ||
+ (count == 1 && (bucket == 17520006 || bucket == 7053950)))
+ << "Only two occupied buckets";
+ }
+}
+
+TEST(FOG, TestCppPings)
+{
+ auto ping = mozilla::glean_pings::OnePingOnly;
+ mozilla::Unused << ping;
+ // That's it. That's the test. It will fail to compile if it's missing.
+ // For a test that actually submits the ping, we have integration tests.
+ // See also bug 1681742.
+}
+
+TEST(FOG, TestCppStringLists)
+{
+ auto kValue = "cheez!"_ns;
+ auto kValue2 = "cheezier!"_ns;
+ auto kValue3 = "cheeziest."_ns;
+
+ nsTArray<nsCString> cheezList;
+ cheezList.EmplaceBack(kValue);
+ cheezList.EmplaceBack(kValue2);
+
+ test_only::cheesy_string_list.Set(cheezList);
+
+ auto val = test_only::cheesy_string_list.TestGetValue().value();
+ // Note: This is fragile if the order is ever not preserved.
+ ASSERT_STREQ(kValue.get(), val[0].get());
+ ASSERT_STREQ(kValue2.get(), val[1].get());
+
+ test_only::cheesy_string_list.Add(kValue3);
+
+ val = test_only::cheesy_string_list.TestGetValue().value();
+ ASSERT_STREQ(kValue3.get(), val[2].get());
+}
+
+TEST(FOG, TestCppTimingDistWorks)
+{
+ auto id1 = test_only::what_time_is_it.Start();
+ auto id2 = test_only::what_time_is_it.Start();
+ PR_Sleep(PR_MillisecondsToInterval(5));
+ auto id3 = test_only::what_time_is_it.Start();
+ test_only::what_time_is_it.Cancel(std::move(id1));
+ PR_Sleep(PR_MillisecondsToInterval(5));
+ test_only::what_time_is_it.StopAndAccumulate(std::move(id2));
+ test_only::what_time_is_it.StopAndAccumulate(std::move(id3));
+
+ DistributionData data = test_only::what_time_is_it.TestGetValue().ref();
+ const uint64_t NANOS_IN_MILLIS = 1e6;
+
+ // We don't know exactly how long those sleeps took, only that it was at
+ // least 15ms total.
+ ASSERT_GT(data.sum, (uint64_t)(15 * NANOS_IN_MILLIS));
+
+ // We also can't guarantee the buckets, but we can guarantee two samples.
+ uint64_t sampleCount = 0;
+ for (auto iter = data.values.Iter(); !iter.Done(); iter.Next()) {
+ sampleCount += iter.UserData();
+ }
+ ASSERT_EQ(sampleCount, (uint64_t)2);
+}
diff --git a/toolkit/components/glean/gtest/TestFogIPC.cpp b/toolkit/components/glean/gtest/TestFogIPC.cpp
new file mode 100644
index 0000000000..f0ac9c85ac
--- /dev/null
+++ b/toolkit/components/glean/gtest/TestFogIPC.cpp
@@ -0,0 +1,47 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+// NOTE: No ContentChild/Parent tests because it includes headers that aren't
+// present in GTest builds (or something).
+
+#include "mozilla/FOGIPC.h"
+#include <functional>
+#include "mozilla/ipc/ByteBuf.h"
+#include "nsTArray.h"
+#include "nsXULAppAPI.h"
+
+using mozilla::ipc::ByteBuf;
+
+TEST(FOG, TestFlushFOGData)
+{
+ // A "It doesn't explode" test.
+ std::function<void(ByteBuf &&)> resolver = [](ByteBuf&& bufs) {};
+ mozilla::glean::FlushFOGData(std::move(resolver));
+}
+
+TEST(FOG, TestFlushAllChildData)
+{
+ std::function<void(const nsTArray<ByteBuf>&&)> resolver =
+ [](const nsTArray<ByteBuf>&& bufs) {
+ ASSERT_TRUE(bufs.Length() == 0)
+ << "Not expecting any bufs yet.";
+ };
+ mozilla::glean::FlushAllChildData(std::move(resolver));
+}
+
+TEST(FOG, FOGData)
+{
+ // Another "It doesn't explode" test.
+ ByteBuf buf;
+ mozilla::glean::FOGData(std::move(buf));
+}
+
+TEST(FOG, SendFOGData)
+{
+ ASSERT_EQ(XRE_GetProcessType(), GeckoProcessType_Default)
+ << "If we can run a test as a different process type, we can write a "
+ "test for this function.";
+}
diff --git a/toolkit/components/glean/gtest/moz.build b/toolkit/components/glean/gtest/moz.build
new file mode 100644
index 0000000000..f70313c7d0
--- /dev/null
+++ b/toolkit/components/glean/gtest/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+if CONFIG["MOZ_GLEAN"]:
+ UNIFIED_SOURCES += [
+ "TestFog.cpp",
+ "TestFogIPC.cpp",
+ ]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/components/glean/gtest/test.rs b/toolkit/components/glean/gtest/test.rs
new file mode 100644
index 0000000000..27af068734
--- /dev/null
+++ b/toolkit/components/glean/gtest/test.rs
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+fn nonfatal_fail(msg: String) {
+ extern "C" {
+ fn GTest_FOG_ExpectFailure(message: *const ::std::os::raw::c_char);
+ }
+ unsafe {
+ let msg = ::std::ffi::CString::new(msg).unwrap();
+ GTest_FOG_ExpectFailure(msg.as_ptr());
+ }
+}
+
+/// This macro checks if the expression evaluates to true,
+/// and causes a non-fatal GTest test failure if it doesn't.
+macro_rules! expect {
+ ($x:expr) => {
+ match (&$x) {
+ true => {}
+ false => nonfatal_fail(format!(
+ "check failed: (`{}`) at {}:{}",
+ stringify!($x),
+ file!(),
+ line!()
+ )),
+ }
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_MeasureInitializeTime() {
+ // At this point FOG is already initialized.
+ // We still need for it to finish, as it is running in a separate thread.
+
+ let metric = &*fog::metrics::fog::initialization;
+ while metric.test_get_value("metrics").is_none() {
+ // We _know_ this value is recorded early, so let's just yield
+ // and try again quickly.
+ std::thread::yield_now();
+ }
+
+ let value = metric.test_get_value("metrics").unwrap();
+ expect!(value > 0);
+}
diff --git a/toolkit/components/glean/ipc/FOGIPC.cpp b/toolkit/components/glean/ipc/FOGIPC.cpp
new file mode 100644
index 0000000000..e4ee0f28ee
--- /dev/null
+++ b/toolkit/components/glean/ipc/FOGIPC.cpp
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "FOGIPC.h"
+
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/MozPromise.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+using mozilla::dom::ContentParent;
+using mozilla::ipc::ByteBuf;
+using FlushFOGDataPromise = mozilla::dom::ContentParent::FlushFOGDataPromise;
+
+namespace mozilla {
+namespace glean {
+
+/**
+ * The parent process is asking you to flush your data ASAP.
+ *
+ * @param aResolver - The function you need to call with the bincoded,
+ * serialized payload that the Rust impl hands you.
+ */
+void FlushFOGData(std::function<void(ipc::ByteBuf&&)>&& aResolver) {
+ ByteBuf buf;
+ uint32_t ipcBufferSize = impl::fog_serialize_ipc_buf();
+ bool ok = buf.Allocate(ipcBufferSize);
+ if (!ok) {
+ return;
+ }
+ uint32_t writtenLen = impl::fog_give_ipc_buf(buf.mData, buf.mLen);
+ if (writtenLen != ipcBufferSize) {
+ return;
+ }
+ aResolver(std::move(buf));
+}
+
+/**
+ * Called by FOG on the parent process when it wants to flush all its
+ * children's data.
+ * @param aResolver - The function that'll be called with the results.
+ */
+void FlushAllChildData(
+ std::function<void(const nsTArray<ipc::ByteBuf>&&)>&& aResolver) {
+ nsTArray<ContentParent*> parents;
+ ContentParent::GetAll(parents);
+ if (parents.Length() == 0) {
+ nsTArray<ipc::ByteBuf> results;
+ aResolver(std::move(results));
+ return;
+ }
+
+ nsTArray<RefPtr<FlushFOGDataPromise>> promises;
+ for (auto parent : parents) {
+ promises.EmplaceBack(parent->SendFlushFOGData());
+ }
+ // TODO: Don't throw away resolved data if some of the promises reject.
+ // (not sure how, but it'll mean not using ::All... maybe a custom copy of
+ // AllPromiseHolder? Might be impossible outside MozPromise.h)
+ FlushFOGDataPromise::All(GetCurrentSerialEventTarget(), promises)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [&aResolver](
+ FlushFOGDataPromise::AllPromiseType::ResolveOrRejectValue&&
+ aValue) {
+ if (aValue.IsResolve()) {
+ aResolver(std::move(aValue.ResolveValue()));
+ } else {
+ nsTArray<ipc::ByteBuf> results;
+ aResolver(std::move(results));
+ }
+ });
+}
+
+/**
+ * A child process has sent you this buf as a treat.
+ * @param buf - a bincoded serialized payload that the Rust impl understands.
+ */
+void FOGData(ipc::ByteBuf&& buf) { impl::fog_use_ipc_buf(buf.mData, buf.mLen); }
+
+/**
+ * Called by FOG on a child process when it wants to send a buf to the parent.
+ * @param buf - a bincoded serialized payload that the Rust impl understands.
+ */
+void SendFOGData(ipc::ByteBuf&& buf) {
+ switch (XRE_GetProcessType()) {
+ case GeckoProcessType_Content:
+ mozilla::dom::ContentChild::GetSingleton()->SendFOGData(std::move(buf));
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsuppored process type");
+ }
+}
+
+} // namespace glean
+} // namespace mozilla
diff --git a/toolkit/components/glean/ipc/FOGIPC.h b/toolkit/components/glean/ipc/FOGIPC.h
new file mode 100644
index 0000000000..1b19f355e7
--- /dev/null
+++ b/toolkit/components/glean/ipc/FOGIPC.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef FOGIPC_h__
+#define FOGIPC_h__
+
+#include <functional>
+
+#include "mozilla/Maybe.h"
+#include "nsTArrayForwardDeclare.h"
+
+namespace mozilla {
+namespace ipc {
+class ByteBuf;
+} // namespace ipc
+} // namespace mozilla
+
+// This module provides the interface for FOG to communicate between processes.
+
+namespace mozilla {
+namespace glean {
+
+/**
+ * The parent process is asking you to flush your data ASAP.
+ *
+ * @param aResolver - The function you need to call with the bincoded,
+ * serialized payload that the Rust impl hands you.
+ */
+void FlushFOGData(std::function<void(ipc::ByteBuf&&)>&& aResolver);
+
+/**
+ * Called by FOG on the parent process when it wants to flush all its
+ * children's data.
+ * @param aResolver - The function that'll be called with the results.
+ */
+void FlushAllChildData(
+ std::function<void(const nsTArray<ipc::ByteBuf>&&)>&& aResolver);
+
+/**
+ * A child process has sent you this buf as a treat.
+ * @param buf - a bincoded serialized payload that the Rust impl understands.
+ */
+void FOGData(ipc::ByteBuf&& buf);
+
+/**
+ * Called by FOG on a child process when it wants to send a buf to the parent.
+ * @param buf - a bincoded serialized payload that the Rust impl understands.
+ */
+void SendFOGData(ipc::ByteBuf&& buf);
+
+} // namespace glean
+} // namespace mozilla
+
+#endif // FOGIPC_h__
diff --git a/toolkit/components/glean/metrics.yaml b/toolkit/components/glean/metrics.yaml
new file mode 100644
index 0000000000..1ae8cfb0c0
--- /dev/null
+++ b/toolkit/components/glean/metrics.yaml
@@ -0,0 +1,109 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+# This file defines the metrics that are recorded by the Glean SDK. They are
+# automatically converted to platform-specific code at build time using the
+# `glean_parser` PyPI package.
+
+# This file is presently for Internal FOG Use Only.
+# You should not add metrics here until probably about January of 2021.
+# If you're looking for the metrics.yaml for Geckoveiw Streaming Telemetry,
+# you can find that one in toolkit/components/telemetry/geckoview/streaming.
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0
+
+fog:
+ initialization:
+ type: timespan
+ time_unit: nanosecond
+ description: |
+ Time the FOG initialization takes.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1662123
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1662123#c3
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - jrediger@mozilla.com
+ - glean-team@mozilla.com
+ expires: never
+
+fog.ipc:
+ replay_failures:
+ type: counter
+ description: |
+ The number of times the ipc buffer failed to be replayed in the
+ parent process.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1664461
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1664461
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
+ expires: never
+
+fog_validation:
+ legacy_telemetry_client_id:
+ type: uuid
+ description:
+ The Telemetry client_id.
+ To be sent only in the "fog-validation" ping.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1674233
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1674233#c4
+ data_sensitivity:
+ - technical
+ - highly_sensitive
+ lifetime: application
+ notification_emails:
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
+ expires: "89"
+ send_in_pings:
+ - fog-validation
+
+ os_version:
+ type: string
+ description:
+ The version of the OS running Firefox, as detected by Gecko.
+ To be sent only in the "fog-validation" ping.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1679835
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1679835#c3
+ data_sensitivity:
+ - technical
+ lifetime: application
+ notification_emails:
+ - aplacitelli@mozilla.com
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
+ expires: "89"
+ send_in_pings:
+ - fog-validation
+
+ profile_disk_is_ssd:
+ type: boolean
+ description:
+ True iff the type of the disk the current Firefox profile is stored on
+ is an SSD. (Windows only).
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675877
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675877#c7
+ data_sensitivity:
+ - technical
+ lifetime: application
+ notification_emails:
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
+ expires: "89"
+ send_in_pings:
+ - fog-validation
diff --git a/toolkit/components/glean/metrics_index.py b/toolkit/components/glean/metrics_index.py
new file mode 100644
index 0000000000..0a8637ce79
--- /dev/null
+++ b/toolkit/components/glean/metrics_index.py
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+# The list of all Glean metrics.yaml files, relative to the top src dir.
+# New additions should be added to the bottom of the list.
+metrics_yamls = [
+ "toolkit/components/glean/metrics.yaml",
+ "toolkit/components/glean/test_metrics.yaml",
+]
+
+# The list of all Glean pings.yaml files, relative to the top src dir.
+# New additions should be added to the bottom of the list.
+pings_yamls = [
+ "toolkit/components/glean/pings.yaml",
+ "toolkit/components/glean/test_pings.yaml",
+]
diff --git a/toolkit/components/glean/moz.build b/toolkit/components/glean/moz.build
new file mode 100644
index 0000000000..5fdbaeb95e
--- /dev/null
+++ b/toolkit/components/glean/moz.build
@@ -0,0 +1,160 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+SPHINX_TREES["/toolkit/components/glean"] = "docs"
+
+# Must be defined unconditionally since the tasks on TC don't know MOZ_GLEAN.
+# (and the tests don't depend on MOZ_GLEAN to run).
+PYTHON_UNITTEST_MANIFESTS += [
+ "pytest/python.ini",
+]
+
+if CONFIG["MOZ_GLEAN"]:
+ # Needed so that we can include IPC things.
+ include("/ipc/chromium/chromium-config.mozbuild")
+
+ FINAL_LIBRARY = "xul"
+
+ EXPORTS.mozilla += [
+ "ipc/FOGIPC.h",
+ ]
+
+ EXPORTS.mozilla.glean += [
+ "!GleanMetrics.h",
+ "!GleanPings.h",
+ ]
+
+ EXPORTS.mozilla.glean.bindings += [
+ "!GleanJSMetricsLookup.h",
+ "!GleanJSPingsLookup.h",
+ "bindings/Category.h",
+ "bindings/Glean.h",
+ "bindings/GleanPings.h",
+ "bindings/MetricTypes.h",
+ "bindings/private/Boolean.h",
+ "bindings/private/Counter.h",
+ "bindings/private/Datetime.h",
+ "bindings/private/DistributionData.h",
+ "bindings/private/Event.h",
+ "bindings/private/MemoryDistribution.h",
+ "bindings/private/Ping.h",
+ "bindings/private/String.h",
+ "bindings/private/StringList.h",
+ "bindings/private/Timespan.h",
+ "bindings/private/TimingDistribution.h",
+ "bindings/private/Uuid.h",
+ ]
+
+ if CONFIG["COMPILE_ENVIRONMENT"]:
+ EXPORTS.mozilla.glean += [
+ "!fog_ffi_generated.h",
+ ]
+
+ CbindgenHeader("fog_ffi_generated.h", inputs=["/toolkit/components/glean"])
+
+ UNIFIED_SOURCES += [
+ "bindings/Category.cpp",
+ "bindings/Glean.cpp",
+ "bindings/GleanPings.cpp",
+ "bindings/private/Boolean.cpp",
+ "bindings/private/Common.cpp",
+ "bindings/private/Counter.cpp",
+ "bindings/private/Datetime.cpp",
+ "bindings/private/Event.cpp",
+ "bindings/private/MemoryDistribution.cpp",
+ "bindings/private/Ping.cpp",
+ "bindings/private/String.cpp",
+ "bindings/private/StringList.cpp",
+ "bindings/private/Timespan.cpp",
+ "bindings/private/TimingDistribution.cpp",
+ "bindings/private/Uuid.cpp",
+ "ipc/FOGIPC.cpp",
+ ]
+
+ TEST_DIRS += [
+ "gtest",
+ ]
+
+ XPCSHELL_TESTS_MANIFESTS += ["xpcshell/xpcshell.ini"]
+
+ # Provides us the list of dependent metrics|pings.yaml.
+ include("metrics_index.py")
+ # GeneratedFile's `inputs` are relative to our dir.
+ # The yamls arrays are relative to topsrcdir, so we need to transform:
+ metrics_yamls = ["../../../" + x for x in metrics_yamls]
+ pings_yamls = ["../../../" + x for x in pings_yamls]
+ # If you change the length of this deps list, update DEPS_LEN in run_glean_parser.py
+ deps = [
+ "metrics_index.py",
+ "build_scripts/glean_parser_ext/cpp.py",
+ "build_scripts/glean_parser_ext/js.py",
+ "build_scripts/glean_parser_ext/run_glean_parser.py",
+ "build_scripts/glean_parser_ext/rust.py",
+ "build_scripts/glean_parser_ext/string_table.py",
+ "build_scripts/glean_parser_ext/util.py",
+ "build_scripts/glean_parser_ext/templates/cpp.jinja2",
+ "build_scripts/glean_parser_ext/templates/cpp_pings.jinja2",
+ "build_scripts/glean_parser_ext/templates/js.jinja2",
+ "build_scripts/glean_parser_ext/templates/js_pings.jinja2",
+ "build_scripts/glean_parser_ext/templates/rust.jinja2",
+ "build_scripts/glean_parser_ext/templates/rust_pings.jinja2",
+ ]
+
+ GeneratedFile(
+ "api/src/metrics.rs",
+ script="build_scripts/glean_parser_ext/run_glean_parser.py",
+ flags=[CONFIG["MOZ_APP_VERSION"]],
+ inputs=deps + metrics_yamls,
+ )
+
+ GeneratedFile(
+ "GleanMetrics.h",
+ script="build_scripts/glean_parser_ext/run_glean_parser.py",
+ entry_point="cpp_metrics",
+ flags=[CONFIG["MOZ_APP_VERSION"]],
+ inputs=deps + metrics_yamls,
+ )
+
+ GeneratedFile(
+ "GleanJSMetricsLookup.h",
+ script="build_scripts/glean_parser_ext/run_glean_parser.py",
+ entry_point="js_metrics",
+ flags=[CONFIG["MOZ_APP_VERSION"]],
+ inputs=deps + metrics_yamls,
+ )
+
+ GeneratedFile(
+ "api/src/pings.rs",
+ script="build_scripts/glean_parser_ext/run_glean_parser.py",
+ flags=[CONFIG["MOZ_APP_VERSION"]],
+ inputs=deps + pings_yamls,
+ )
+
+ GeneratedFile(
+ "GleanPings.h",
+ script="build_scripts/glean_parser_ext/run_glean_parser.py",
+ entry_point="cpp_metrics",
+ flags=[CONFIG["MOZ_APP_VERSION"]],
+ inputs=deps + pings_yamls,
+ )
+
+ GeneratedFile(
+ "GleanJSPingsLookup.h",
+ script="build_scripts/glean_parser_ext/run_glean_parser.py",
+ entry_point="js_metrics",
+ flags=[CONFIG["MOZ_APP_VERSION"]],
+ inputs=deps + pings_yamls,
+ )
+
+ DIRS += [
+ "xpcom",
+ ]
+
+with Files("docs/**"):
+ SCHEDULES.exclusive = ["docs"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Telemetry")
diff --git a/toolkit/components/glean/pings.yaml b/toolkit/components/glean/pings.yaml
new file mode 100644
index 0000000000..aae6faee37
--- /dev/null
+++ b/toolkit/components/glean/pings.yaml
@@ -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 http://mozilla.org/MPL/2.0/.
+
+# This file defines the pings that are recorded by the Glean SDK. They are
+# automatically converted to platform-specific code at build time using the
+# `glean_parser` PyPI package.
+
+# This file is presently for Internal FOG Use Only.
+# You should not add pings here until probably about January of 2021.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/1-0-0
+
+fog-validation:
+ description: |
+ This ping is intended to evaluate the behaviour of FOG before
+ it ships beyond Nightly.
+ This is a temporary ping.
+ It is sent one hour after FOG is initialized, and every hour thereafter.
+ include_client_id: true
+ send_if_empty: true
+ bugs:
+ - https://bugzilla.mozilla.org/1664461
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1664461
+ notification_emails:
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
diff --git a/toolkit/components/glean/pytest/metrics_expires_number_test.yaml b/toolkit/components/glean/pytest/metrics_expires_number_test.yaml
new file mode 100644
index 0000000000..701bc00443
--- /dev/null
+++ b/toolkit/components/glean/pytest/metrics_expires_number_test.yaml
@@ -0,0 +1,22 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+# This file is FOR TESTING PURPOSES ONLY.
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0
+
+test:
+ expires_number:
+ type: boolean
+ expires: 99
+ description: |
+ A metric with an expires of an invalid type (number)
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
diff --git a/toolkit/components/glean/pytest/metrics_expires_versions_test.yaml b/toolkit/components/glean/pytest/metrics_expires_versions_test.yaml
new file mode 100644
index 0000000000..7520f749e5
--- /dev/null
+++ b/toolkit/components/glean/pytest/metrics_expires_versions_test.yaml
@@ -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 http://mozilla.org/MPL/2.0/.
+
+# This file is FOR TESTING PURPOSES ONLY.
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0
+
+test:
+ expired1:
+ type: boolean
+ expires: "41"
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+ no_lint:
+ - EXPIRED
+
+ expired2:
+ type: labeled_boolean
+ expires: "42"
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+ no_lint:
+ - EXPIRED
+
+ unexpired:
+ type: labeled_boolean
+ expires: "100"
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+ labels:
+ - one_label
+ - two_labels
+
+ never:
+ type: string
+ expires: never
+ description: A never-expiring metric
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+
+ always:
+ type: string
+ expires: expired
+ description: An already-expired metric
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+ no_lint:
+ - EXPIRED
diff --git a/toolkit/components/glean/pytest/metrics_test.yaml b/toolkit/components/glean/pytest/metrics_test.yaml
new file mode 100644
index 0000000000..ead9217466
--- /dev/null
+++ b/toolkit/components/glean/pytest/metrics_test.yaml
@@ -0,0 +1,270 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+# This file defines the metrics that are recorded by the Glean SDK. They are
+# automatically converted to platform-specific code at build time using the
+# `glean_parser` PyPI package.
+
+# This file is presently for Internal FOG Use Only.
+# You should not add metrics here until probably about January of 2021.
+# If you're looking for the metrics.yaml for Geckoveiw Streaming Telemetry,
+# you can find that one in toolkit/components/telemetry/geckoview/streaming.
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0
+
+test:
+ boolean_metric:
+ type: boolean
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ labeled_boolean_metric:
+ type: labeled_boolean
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ labeled_boolean_metric_labels:
+ type: labeled_boolean
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ labels:
+ - one_label
+ - two_labels
+
+ counter_metric:
+ type: counter
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ labeled_counter_metric:
+ type: labeled_counter
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ labeled_counter_metric_labels:
+ type: labeled_counter
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ labels:
+ - one_label
+ - two_labels
+
+ string_metric:
+ type: string
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ labeled_string_metric:
+ type: labeled_string
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ labeled_string_metric_labels:
+ type: labeled_string
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ labels:
+ - one_label
+ - two_labels
+
+ string_list_metric:
+ type: string_list
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ timespan_metric:
+ type: timespan
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ timing_distribution_metric:
+ type: timing_distribution
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ memory_distribution_metric:
+ type: memory_distribution
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ memory_unit: kilobyte
+
+test.nested:
+ uuid_metric:
+ type: uuid
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ datetime_metric:
+ type: datetime
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ event_metric:
+ type: event
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: ping
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ event_metric_with_extra:
+ type: event
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: ping
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ extra_keys:
+ an_extra_key:
+ description: An extra key description
+ another_extra_key:
+ description: Another extra key description
diff --git a/toolkit/components/glean/pytest/metrics_test_output b/toolkit/components/glean/pytest/metrics_test_output
new file mode 100644
index 0000000000..0ac9f7b9ba
--- /dev/null
+++ b/toolkit/components/glean/pytest/metrics_test_output
@@ -0,0 +1,542 @@
+// -*- mode: Rust -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+
+pub mod test {
+ use crate::private::*;
+ use glean_core::CommonMetricData;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.boolean_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static boolean_metric: Lazy<BooleanMetric> = Lazy::new(|| {
+ BooleanMetric::new(1.into(), CommonMetricData {
+ name: "boolean_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_boolean_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_boolean_metric: Lazy<LabeledMetric<BooleanMetric>> = Lazy::new(|| {
+ LabeledMetric::new(2.into(), CommonMetricData {
+ name: "labeled_boolean_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, None)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_boolean_metric_labels
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_boolean_metric_labels: Lazy<LabeledMetric<BooleanMetric>> = Lazy::new(|| {
+ LabeledMetric::new(3.into(), CommonMetricData {
+ name: "labeled_boolean_metric_labels".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, Some(vec!["one_label".into(), "two_labels".into()]))
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.counter_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static counter_metric: Lazy<CounterMetric> = Lazy::new(|| {
+ CounterMetric::new(4.into(), CommonMetricData {
+ name: "counter_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_counter_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_counter_metric: Lazy<LabeledMetric<CounterMetric>> = Lazy::new(|| {
+ LabeledMetric::new(5.into(), CommonMetricData {
+ name: "labeled_counter_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, None)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_counter_metric_labels
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_counter_metric_labels: Lazy<LabeledMetric<CounterMetric>> = Lazy::new(|| {
+ LabeledMetric::new(6.into(), CommonMetricData {
+ name: "labeled_counter_metric_labels".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, Some(vec!["one_label".into(), "two_labels".into()]))
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.string_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static string_metric: Lazy<StringMetric> = Lazy::new(|| {
+ StringMetric::new(7.into(), CommonMetricData {
+ name: "string_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_string_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_string_metric: Lazy<LabeledMetric<StringMetric>> = Lazy::new(|| {
+ LabeledMetric::new(8.into(), CommonMetricData {
+ name: "labeled_string_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, None)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_string_metric_labels
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_string_metric_labels: Lazy<LabeledMetric<StringMetric>> = Lazy::new(|| {
+ LabeledMetric::new(9.into(), CommonMetricData {
+ name: "labeled_string_metric_labels".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, Some(vec!["one_label".into(), "two_labels".into()]))
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.string_list_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static string_list_metric: Lazy<StringListMetric> = Lazy::new(|| {
+ StringListMetric::new(10.into(), CommonMetricData {
+ name: "string_list_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.timespan_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static timespan_metric: Lazy<TimespanMetric> = Lazy::new(|| {
+ TimespanMetric::new(11.into(), CommonMetricData {
+ name: "timespan_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, TimeUnit::Millisecond)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.timing_distribution_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static timing_distribution_metric: Lazy<TimingDistributionMetric> = Lazy::new(|| {
+ TimingDistributionMetric::new(12.into(), CommonMetricData {
+ name: "timing_distribution_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, TimeUnit::Nanosecond)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.memory_distribution_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static memory_distribution_metric: Lazy<MemoryDistributionMetric> = Lazy::new(|| {
+ MemoryDistributionMetric::new(13.into(), CommonMetricData {
+ name: "memory_distribution_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, MemoryUnit::Kilobyte)
+ });
+
+}
+pub mod test_nested {
+ use crate::private::*;
+ use glean_core::CommonMetricData;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.uuid_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static uuid_metric: Lazy<UuidMetric> = Lazy::new(|| {
+ UuidMetric::new(14.into(), CommonMetricData {
+ name: "uuid_metric".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.datetime_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static datetime_metric: Lazy<DatetimeMetric> = Lazy::new(|| {
+ DatetimeMetric::new(15.into(), CommonMetricData {
+ name: "datetime_metric".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, TimeUnit::Millisecond)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.event_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static event_metric: Lazy<EventMetric<NoExtraKeys>> = Lazy::new(|| {
+ EventMetric::new(16.into(), CommonMetricData {
+ name: "event_metric".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["events".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[derive(Clone, Copy, Hash, Eq, PartialEq)]
+ pub enum EventMetricWithExtraKeys {
+ AnExtraKey,
+ AnotherExtraKey,
+ }
+
+ impl ExtraKeys for EventMetricWithExtraKeys {
+ const ALLOWED_KEYS: &'static [&'static str] = &["an_extra_key", "another_extra_key"];
+
+ fn index(self) -> i32 {
+ self as i32
+ }
+ }
+
+ /// Convert from an extra key's index to its variant.
+ impl std::convert::TryFrom<i32> for EventMetricWithExtraKeys {
+ type Error = EventRecordingError;
+
+ fn try_from(value: i32) -> Result<Self, Self::Error> {
+ match value {
+ 0 => Ok(Self::AnExtraKey),
+ 1 => Ok(Self::AnotherExtraKey),
+ _ => Err(EventRecordingError::InvalidExtraKey),
+ }
+ }
+ }
+
+ /// Convert from an extra key's string representation to its variant.
+ impl std::convert::TryFrom<&str> for EventMetricWithExtraKeys {
+ type Error = EventRecordingError;
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ match value {
+ "an_extra_key" => Ok(Self::AnExtraKey),
+ "another_extra_key" => Ok(Self::AnotherExtraKey),
+ _ => Err(EventRecordingError::InvalidExtraKey),
+ }
+ }
+ }
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.event_metric_with_extra
+ ///
+ /// A multi-line
+ /// description
+ pub static event_metric_with_extra: Lazy<EventMetric<EventMetricWithExtraKeys>> = Lazy::new(|| {
+ EventMetric::new(17.into(), CommonMetricData {
+ name: "event_metric_with_extra".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["events".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+}
+
+#[allow(dead_code)]
+pub(crate) mod __glean_metric_maps {
+ use std::collections::HashMap;
+ use std::convert::TryInto;
+
+ use crate::private::*;
+ use once_cell::sync::Lazy;
+
+ pub static BOOLEAN_MAP: Lazy<HashMap<MetricId, &Lazy<BooleanMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(1.into(), &super::test::boolean_metric);
+ map
+ });
+
+ pub static LABELED_BOOLEAN_MAP: Lazy<HashMap<MetricId, &Lazy<LabeledMetric<BooleanMetric>>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(2);
+ map.insert(2.into(), &super::test::labeled_boolean_metric);
+ map.insert(3.into(), &super::test::labeled_boolean_metric_labels);
+ map
+ });
+
+ pub static COUNTER_MAP: Lazy<HashMap<MetricId, &Lazy<CounterMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(4.into(), &super::test::counter_metric);
+ map
+ });
+
+ pub static LABELED_COUNTER_MAP: Lazy<HashMap<MetricId, &Lazy<LabeledMetric<CounterMetric>>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(2);
+ map.insert(5.into(), &super::test::labeled_counter_metric);
+ map.insert(6.into(), &super::test::labeled_counter_metric_labels);
+ map
+ });
+
+ pub static STRING_MAP: Lazy<HashMap<MetricId, &Lazy<StringMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(7.into(), &super::test::string_metric);
+ map
+ });
+
+ pub static LABELED_STRING_MAP: Lazy<HashMap<MetricId, &Lazy<LabeledMetric<StringMetric>>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(2);
+ map.insert(8.into(), &super::test::labeled_string_metric);
+ map.insert(9.into(), &super::test::labeled_string_metric_labels);
+ map
+ });
+
+ pub static STRING_LIST_MAP: Lazy<HashMap<MetricId, &Lazy<StringListMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(10.into(), &super::test::string_list_metric);
+ map
+ });
+
+ pub static TIMESPAN_MAP: Lazy<HashMap<MetricId, &Lazy<TimespanMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(11.into(), &super::test::timespan_metric);
+ map
+ });
+
+ pub static TIMING_DISTRIBUTION_MAP: Lazy<HashMap<MetricId, &Lazy<TimingDistributionMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(12.into(), &super::test::timing_distribution_metric);
+ map
+ });
+
+ pub static MEMORY_DISTRIBUTION_MAP: Lazy<HashMap<MetricId, &Lazy<MemoryDistributionMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(13.into(), &super::test::memory_distribution_metric);
+ map
+ });
+
+ pub static UUID_MAP: Lazy<HashMap<MetricId, &Lazy<UuidMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(14.into(), &super::test_nested::uuid_metric);
+ map
+ });
+
+ pub static DATETIME_MAP: Lazy<HashMap<MetricId, &Lazy<DatetimeMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(15.into(), &super::test_nested::datetime_metric);
+ map
+ });
+
+
+ /// 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()
+ }
+
+ /// Wrapper to record an event based on its metric ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `extra` - An (optional) map of (extra key id, string) pairs.
+ /// The map will be decoded into the appropriate `ExtraKeys` type.
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the event was found and `record` was called with the given `extra`,
+ /// or an `EventRecordingError::InvalidId` if no event by that ID exists
+ /// or an `EventRecordingError::InvalidExtraKey` the `extra` map could not be deserialized.
+ pub(crate) fn event_record_wrapper(metric_id: u32, extra: HashMap<i32, String>) -> Result<(), EventRecordingError> {
+ match metric_id {
+ 16 => {
+ assert!(
+ extra_keys_len(&super::test_nested::event_metric) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ // In case of `NoExtraKeys` the whole iterator is impossible, so rustc complains.
+ #[allow(unused_variables)]
+ let extra: HashMap<_, _> = extra
+ .into_iter()
+ .map(|(k, v)| k.try_into().map(|k| (k, v)))
+ .collect::<Result<HashMap<_, _>, _>>()?;
+ super::test_nested::event_metric.record(Some(extra));
+ Ok(())
+ }
+ 17 => {
+ assert!(
+ extra_keys_len(&super::test_nested::event_metric_with_extra) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ // In case of `NoExtraKeys` the whole iterator is impossible, so rustc complains.
+ #[allow(unused_variables)]
+ let extra: HashMap<_, _> = extra
+ .into_iter()
+ .map(|(k, v)| k.try_into().map(|k| (k, v)))
+ .collect::<Result<HashMap<_, _>, _>>()?;
+ super::test_nested::event_metric_with_extra.record(Some(extra));
+ Ok(())
+ }
+ _ => Err(EventRecordingError::InvalidId),
+ }
+ }
+
+ /// Wrapper to record an event based on its metric ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `extra` - An (optional) map of (string, string) pairs.
+ /// The map will be decoded into the appropriate `ExtraKeys` types.
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the event was found and `record` was called with the given `extra`,
+ /// or an `EventRecordingError::InvalidId` if no event by that ID exists
+ /// or an `EventRecordingError::InvalidExtraKey` the `extra` map could not be deserialized.
+ pub(crate) fn event_record_wrapper_str(metric_id: u32, extra: HashMap<String, String>) -> Result<(), EventRecordingError> {
+ match metric_id {
+ 16 => {
+ assert!(
+ extra_keys_len(&super::test_nested::event_metric) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ // In case of `NoExtraKeys` the whole iterator is impossible, so rustc complains.
+ #[allow(unused_variables)]
+ let extra = extra
+ .into_iter()
+ .map(|(k, v)| (&*k).try_into().map(|k| (k, v)))
+ .collect::<Result<HashMap<_, _>, _>>()?;
+ super::test_nested::event_metric.record(Some(extra));
+ Ok(())
+ }
+ 17 => {
+ assert!(
+ extra_keys_len(&super::test_nested::event_metric_with_extra) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ // In case of `NoExtraKeys` the whole iterator is impossible, so rustc complains.
+ #[allow(unused_variables)]
+ let extra = extra
+ .into_iter()
+ .map(|(k, v)| (&*k).try_into().map(|k| (k, v)))
+ .collect::<Result<HashMap<_, _>, _>>()?;
+ super::test_nested::event_metric_with_extra.record(Some(extra));
+ Ok(())
+ }
+ _ => Err(EventRecordingError::InvalidId),
+ }
+ }
+
+ /// Wrapper to get the currently stored events for event metric.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `storage_name` - the storage name to look into.
+ ///
+ /// # Returns
+ ///
+ /// Returns the recorded events or `None` if nothing stored.
+ ///
+ /// # Panics
+ ///
+ /// Panics if no event by the given metric ID could be found.
+ pub(crate) fn event_test_get_value_wrapper(metric_id: u32, storage_name: &str) -> Option<Vec<RecordedEvent>> {
+ match metric_id {
+ 16 => super::test_nested::event_metric.test_get_value(storage_name),
+ 17 => super::test_nested::event_metric_with_extra.test_get_value(storage_name),
+ _ => panic!("No event for metric id {}", metric_id),
+ }
+ }
+}
+
diff --git a/toolkit/components/glean/pytest/metrics_test_output_cpp b/toolkit/components/glean/pytest/metrics_test_output_cpp
new file mode 100644
index 0000000000..73cfc76bc3
--- /dev/null
+++ b/toolkit/components/glean/pytest/metrics_test_output_cpp
@@ -0,0 +1,130 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Metrics_h
+#define mozilla_Metrics_h
+
+#include "mozilla/glean/bindings/MetricTypes.h"
+
+namespace mozilla::glean {
+
+enum class NoExtraKeys {};
+
+
+namespace test {
+ /**
+ * generated from test.boolean_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::BooleanMetric boolean_metric(1);
+
+ /**
+ * generated from test.counter_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::CounterMetric counter_metric(4);
+
+ /**
+ * generated from test.string_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::StringMetric string_metric(7);
+
+ /**
+ * generated from test.string_list_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::StringListMetric string_list_metric(10);
+
+ /**
+ * generated from test.timespan_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::TimespanMetric timespan_metric(11);
+
+ /**
+ * generated from test.timing_distribution_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::TimingDistributionMetric timing_distribution_metric(12);
+
+ /**
+ * generated from test.memory_distribution_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::MemoryDistributionMetric memory_distribution_metric(13);
+
+}
+namespace test_nested {
+ /**
+ * generated from test.nested.uuid_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::UuidMetric uuid_metric(14);
+
+ /**
+ * generated from test.nested.datetime_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::DatetimeMetric datetime_metric(15);
+
+ /**
+ * generated from test.nested.event_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::EventMetric<uint32_t> event_metric(16);
+
+ /**
+ * generated from test.nested.event_metric_with_extra
+ */
+ enum class EventMetricWithExtraKeys : int32_t {
+ AnExtraKey,
+ AnotherExtraKey,
+ };
+
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::EventMetric<EventMetricWithExtraKeys> event_metric_with_extra(17);
+
+}
+
+} // namespace mozilla::glean
+
+#endif // mozilla_Metrics_h
diff --git a/toolkit/components/glean/pytest/metrics_test_output_js b/toolkit/components/glean/pytest/metrics_test_output_js
new file mode 100644
index 0000000000..b9c351baa0
--- /dev/null
+++ b/toolkit/components/glean/pytest/metrics_test_output_js
@@ -0,0 +1,255 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GleanJSMetricsLookup_h
+#define mozilla_GleanJSMetricsLookup_h
+
+#include "mozilla/PerfectHash.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/glean/bindings/MetricTypes.h"
+
+#define GLEAN_INDEX_BITS (32)
+#define GLEAN_ID_BITS (27)
+#define GLEAN_TYPE_ID(id) ((id) >> GLEAN_ID_BITS)
+#define GLEAN_METRIC_ID(id) ((id) & ((1ULL << GLEAN_ID_BITS) - 1))
+#define GLEAN_OFFSET(entry) (entry & ((1ULL << GLEAN_INDEX_BITS) - 1))
+
+namespace mozilla::glean {
+
+// The category lookup table's entry type
+using category_entry_t = uint32_t;
+// The metric lookup table's entry type
+using metric_entry_t = uint64_t;
+
+static_assert(GLEAN_INDEX_BITS + GLEAN_ID_BITS < sizeof(metric_entry_t) * 8, "Index and ID bits need to fit into an category_entry_t");
+static_assert(GLEAN_ID_BITS < sizeof(uint32_t) * 8, "Metric IDs need to fit into less than 32 bit");
+static_assert(2 < UINT32_MAX, "Too many metric categories generated.");
+static_assert(17 < 134217728, "Too many metrics generated.");
+static_assert(13 < 32, "Too many different metric types.");
+
+static already_AddRefed<nsISupports> NewMetricFromId(uint32_t id) {
+ uint32_t typeId = GLEAN_TYPE_ID(id);
+ uint32_t metricId = GLEAN_METRIC_ID(id);
+
+ switch (typeId) {
+ case 1: /* Boolean */
+ {
+ return MakeAndAddRef<GleanBoolean>(metricId);
+ }
+ case 3: /* Counter */
+ {
+ return MakeAndAddRef<GleanCounter>(metricId);
+ }
+ case 5: /* String */
+ {
+ return MakeAndAddRef<GleanString>(metricId);
+ }
+ case 7: /* StringList */
+ {
+ return MakeAndAddRef<GleanStringList>(metricId);
+ }
+ case 8: /* Timespan */
+ {
+ return MakeAndAddRef<GleanTimespan>(metricId);
+ }
+ case 9: /* TimingDistribution */
+ {
+ return MakeAndAddRef<GleanTimingDistribution>(metricId);
+ }
+ case 10: /* MemoryDistribution */
+ {
+ return MakeAndAddRef<GleanMemoryDistribution>(metricId);
+ }
+ case 11: /* Uuid */
+ {
+ return MakeAndAddRef<GleanUuid>(metricId);
+ }
+ case 12: /* Datetime */
+ {
+ return MakeAndAddRef<GleanDatetime>(metricId);
+ }
+ case 13: /* Event */
+ {
+ return MakeAndAddRef<GleanEvent>(metricId);
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid type ID reached when trying to instantiate a new metric");
+ return nullptr;
+ }
+}
+
+static Maybe<uint32_t> category_result_check(const nsACString& aKey, category_entry_t entry);
+static Maybe<uint32_t> metric_result_check(const nsACString& aKey, metric_entry_t entry);
+
+#if defined(_MSC_VER) && !defined(__clang__)
+const char gCategoryStringTable[] = {
+#else
+constexpr char gCategoryStringTable[] = {
+#endif
+ /* 0 - "test" */ 't', 'e', 's', 't', '\0',
+ /* 5 - "testNested" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '\0',
+};
+
+
+static_assert(sizeof(gCategoryStringTable) < UINT32_MAX, "Category string table is too large.");
+
+const category_entry_t sCategoryByNameLookupEntries[] = {
+ 5,
+ 0
+};
+
+
+
+static Maybe<uint32_t>
+CategoryByNameLookup(const nsACString& aKey)
+{
+ static const uint8_t BASES[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+
+
+ const char* bytes = aKey.BeginReading();
+ size_t length = aKey.Length();
+ auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES,
+ sCategoryByNameLookupEntries);
+ return category_result_check(aKey, entry);
+}
+
+
+#if defined(_MSC_VER) && !defined(__clang__)
+const char gMetricStringTable[] = {
+#else
+constexpr char gMetricStringTable[] = {
+#endif
+ /* 0 - "test.booleanMetric" */ 't', 'e', 's', 't', '.', 'b', 'o', 'o', 'l', 'e', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 19 - "test.labeledBooleanMetric" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 45 - "test.labeledBooleanMetricLabels" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', 'L', 'a', 'b', 'e', 'l', 's', '\0',
+ /* 77 - "test.counterMetric" */ 't', 'e', 's', 't', '.', 'c', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 96 - "test.labeledCounterMetric" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 122 - "test.labeledCounterMetricLabels" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', 'L', 'a', 'b', 'e', 'l', 's', '\0',
+ /* 154 - "test.stringMetric" */ 't', 'e', 's', 't', '.', 's', 't', 'r', 'i', 'n', 'g', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 172 - "test.labeledStringMetric" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'S', 't', 'r', 'i', 'n', 'g', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 197 - "test.labeledStringMetricLabels" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'S', 't', 'r', 'i', 'n', 'g', 'M', 'e', 't', 'r', 'i', 'c', 'L', 'a', 'b', 'e', 'l', 's', '\0',
+ /* 228 - "test.stringListMetric" */ 't', 'e', 's', 't', '.', 's', 't', 'r', 'i', 'n', 'g', 'L', 'i', 's', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 250 - "test.timespanMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'e', 's', 'p', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 270 - "test.timingDistributionMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'i', 'n', 'g', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 300 - "test.memoryDistributionMetric" */ 't', 'e', 's', 't', '.', 'm', 'e', 'm', 'o', 'r', 'y', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 330 - "testNested.uuidMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'u', 'u', 'i', 'd', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 352 - "testNested.datetimeMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'd', 'a', 't', 'e', 't', 'i', 'm', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 378 - "testNested.eventMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 401 - "testNested.eventMetricWithExtra" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', 'W', 'i', 't', 'h', 'E', 'x', 't', 'r', 'a', '\0',
+};
+
+
+static_assert(sizeof(gMetricStringTable) < 4294967296, "Metric string table is too large.");
+
+const metric_entry_t sMetricByNameLookupEntries[] = {
+ 1729382274090139725,
+ 3458764552475246789,
+ 1152921513196781587,
+ 2305843030688530528,
+ 5188146822270419214,
+ 7493989848663982458,
+ 1152921517491748909,
+ 576460756598390784,
+ 4035225309073637604,
+ 6341068335467200842,
+ 6917529092065591648,
+ 4611686065672028410,
+ 7493989852958949777,
+ 2305843034983497850,
+ 5764607578868810028,
+ 3458764548180279468,
+ 2882303791581888666
+};
+
+
+
+static Maybe<uint32_t>
+MetricByNameLookup(const nsACString& aKey)
+{
+ static const uint8_t BASES[] = {
+ 0, 0, 0, 2, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 2, 0, 0, 0, 1, 0, 0, 1, 0, 5, 0, 0, 4, 0, 1, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 2, 0, 6, 0, 0,
+ 0, 0, 20, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10,
+ };
+
+
+ const char* bytes = aKey.BeginReading();
+ size_t length = aKey.Length();
+ auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES,
+ sMetricByNameLookupEntries);
+ return metric_result_check(aKey, entry);
+}
+
+
+/**
+ * Get a category's name from the string table.
+ */
+static const char* GetCategoryName(category_entry_t entry) {
+ MOZ_ASSERT(entry < sizeof(gCategoryStringTable), "Entry identifier offset larger than string table");
+ return &gCategoryStringTable[entry];
+}
+
+/**
+ * Get a metric's identifier from the string table.
+ */
+static const char* GetMetricIdentifier(metric_entry_t entry) {
+ uint32_t offset = GLEAN_OFFSET(entry);
+ MOZ_ASSERT(offset < sizeof(gMetricStringTable), "Entry identifier offset larger than string table");
+ return &gMetricStringTable[offset];
+}
+
+/**
+ * Check that the found entry is pointing to the right key
+ * and return it.
+ * Or return `Nothing()` if the entry was not found.
+ */
+static Maybe<uint32_t> category_result_check(const nsACString& aKey, category_entry_t entry) {
+ if (MOZ_UNLIKELY(entry > sizeof(gCategoryStringTable))) {
+ return Nothing();
+ }
+ if (aKey.EqualsASCII(gCategoryStringTable + entry)) {
+ return Some(entry);
+ }
+ return Nothing();
+}
+
+/**
+ * Check if the found entry index is pointing to the right key
+ * and return the corresponding metric ID.
+ * Or return `Nothing()` if the entry was not found.
+ */
+static Maybe<uint32_t> metric_result_check(const nsACString& aKey, uint64_t entry) {
+ uint32_t metricId = entry >> GLEAN_INDEX_BITS;
+ uint32_t offset = GLEAN_OFFSET(entry);
+
+ if (offset > sizeof(gMetricStringTable)) {
+ return Nothing();
+ }
+
+ if (aKey.EqualsASCII(gMetricStringTable + offset)) {
+ return Some(metricId);
+ }
+
+ return Nothing();
+}
+
+
+#undef GLEAN_INDEX_BITS
+#undef GLEAN_ID_BITS
+#undef GLEAN_TYPE_ID
+#undef GLEAN_METRIC_ID
+#undef GLEAN_OFFSET
+
+} // namespace mozilla::glean
+#endif // mozilla_GleanJSMetricsLookup_h
diff --git a/toolkit/components/glean/pytest/pings_test.yaml b/toolkit/components/glean/pytest/pings_test.yaml
new file mode 100644
index 0000000000..efa6ba9e0a
--- /dev/null
+++ b/toolkit/components/glean/pytest/pings_test.yaml
@@ -0,0 +1,116 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+# This file defines the built-in pings that are recorded by the Glean SDK. They
+# are automatically converted to Kotlin code at build time using the
+# `glean_parser` PyPI package.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/1-0-0
+
+not-baseline:
+ description: >
+ This ping is intended to provide metrics that are managed by the library
+ itself, and not explicitly set by the application or included in the
+ application's `metrics.yaml` file.
+ The `baseline` ping is automatically sent when the application is moved to
+ the background.
+ include_client_id: true
+ bugs:
+ - https://bugzilla.mozilla.org/1512938
+ - https://bugzilla.mozilla.org/1599877
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1512938#c3
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1599877#c25
+ notification_emails:
+ - glean-team@mozilla.com
+ reasons:
+ dirty_startup: |
+ The ping was submitted at startup, because the application process was
+ killed before the Glean SDK had the chance to generate this ping, when
+ going to background, in the last session.
+
+ *Note*: this ping will not contain the `glean.baseline.duration` metric.
+ background: |
+ The ping was submitted before going to background.
+ foreground: |
+ The ping was submitted when the application went to foreground, which
+ includes when the application starts.
+
+ *Note*: this ping will not contain the `glean.baseline.duration` metric.
+
+not-metrics:
+ description: >
+ The `metrics` ping is intended for all of the metrics that are explicitly
+ set by the application or are included in the application's `metrics.yaml`
+ file (except events).
+ The reported data is tied to the ping's *measurement window*, which is the
+ time between the collection of two `metrics` ping. Ideally, this window is
+ expected to be about 24 hours, given that the collection is scheduled daily
+ at 4AM. Data in the `ping_info` section of the ping can be used to infer the
+ length of this window.
+ include_client_id: true
+ bugs:
+ - https://bugzilla.mozilla.org/1512938
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1512938#c3
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1557048#c13
+ notification_emails:
+ - glean-team@mozilla.com
+ reasons:
+ overdue: |
+ The last ping wasn't submitted on the current calendar day, but it's after
+ 4am, so this ping submitted immediately
+ today: |
+ The last ping wasn't submitted on the current calendar day, but it is
+ still before 4am, so schedule to send this ping on the current calendar
+ day at 4am.
+ tomorrow: |
+ The last ping was already submitted on the current calendar day, so
+ schedule this ping for the next calendar day at 4am.
+ upgrade: |
+ This ping was submitted at startup because the application was just
+ upgraded.
+ reschedule: |
+ A ping was just submitted. This ping was rescheduled for the next calendar
+ day at 4am.
+
+not-events:
+ description: >
+ The events ping's purpose is to transport all of the event metric
+ information. The `events` ping is automatically sent when the application is
+ moved to the background.
+ include_client_id: true
+ bugs:
+ - https://bugzilla.mozilla.org/1512938
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1512938#c3
+ notification_emails:
+ - glean-team@mozilla.com
+ reasons:
+ startup: |
+ The ping was submitted at startup. The events ping is always sent if there
+ are any pending events at startup, because event timestamps can not be
+ mixed across runs of the application.
+ background: |
+ The ping was submitted before going to background.
+ max_capacity: |
+ The maximum number of events was reached (default 500 events).
+
+not-deletion-request:
+ description: >
+ This ping is submitted when a user opts out of
+ sending technical and interaction data to Mozilla.
+ This ping is intended to communicate to the Data Pipeline
+ that the user wishes to have their reported Telemetry data deleted.
+ As such it attempts to send itself at the moment the user
+ opts out of data collection.
+ include_client_id: true
+ send_if_empty: true
+ bugs:
+ - https://bugzilla.mozilla.org/1587095
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1587095#c6
+ notification_emails:
+ - glean-team@mozilla.com
diff --git a/toolkit/components/glean/pytest/pings_test_output b/toolkit/components/glean/pytest/pings_test_output
new file mode 100644
index 0000000000..df705967bc
--- /dev/null
+++ b/toolkit/components/glean/pytest/pings_test_output
@@ -0,0 +1,93 @@
+// -*- mode: Rust -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+use crate::private::Ping;
+use once_cell::sync::Lazy;
+
+#[allow(non_upper_case_globals)]
+/// This ping is intended to provide metrics that are managed by the library
+/// itself, and not explicitly set by the application or included in the
+/// application's `metrics.yaml` file. The `baseline` ping is automatically sent
+/// when the application is moved to the background.
+pub static not_baseline: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "not-baseline",
+ true,
+ false,
+ vec!["background".into(), "dirty_startup".into(), "foreground".into()],
+ )
+});
+
+#[allow(non_upper_case_globals)]
+/// The `metrics` ping is intended for all of the metrics that are explicitly set
+/// by the application or are included in the application's `metrics.yaml` file
+/// (except events). The reported data is tied to the ping's *measurement window*,
+/// which is the time between the collection of two `metrics` ping. Ideally, this
+/// window is expected to be about 24 hours, given that the collection is scheduled
+/// daily at 4AM. Data in the `ping_info` section of the ping can be used to infer
+/// the length of this window.
+pub static not_metrics: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "not-metrics",
+ true,
+ false,
+ vec!["overdue".into(), "reschedule".into(), "today".into(), "tomorrow".into(), "upgrade".into()],
+ )
+});
+
+#[allow(non_upper_case_globals)]
+/// The events ping's purpose is to transport all of the event metric information.
+/// The `events` ping is automatically sent when the application is moved to the
+/// background.
+pub static not_events: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "not-events",
+ true,
+ false,
+ vec!["background".into(), "max_capacity".into(), "startup".into()],
+ )
+});
+
+#[allow(non_upper_case_globals)]
+/// This ping is submitted when a user opts out of sending technical and
+/// interaction data to Mozilla. This ping is intended to communicate to the Data
+/// Pipeline that the user wishes to have their reported Telemetry data deleted. As
+/// such it attempts to send itself at the moment the user opts out of data
+/// collection.
+pub static not_deletion_request: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "not-deletion-request",
+ true,
+ true,
+ vec![],
+ )
+});
+
+
+/// Instantiate each custom ping once to trigger registration.
+#[doc(hidden)]
+pub fn register_pings() {
+ let _ = &*not_baseline;
+ let _ = &*not_metrics;
+ let _ = &*not_events;
+ let _ = &*not_deletion_request;
+}
+
+#[cfg(feature = "with_gecko")]
+pub(crate) fn submit_ping_by_id(id: u32, reason: Option<&str>) {
+ match id {
+ 1 => not_baseline.submit(reason),
+ 2 => not_metrics.submit(reason),
+ 3 => not_events.submit(reason),
+ 4 => not_deletion_request.submit(reason),
+ _ => {
+ // TODO: instrument this error.
+ log::error!("Cannot submit unknown ping {} by id.", id);
+ }
+ }
+}
diff --git a/toolkit/components/glean/pytest/pings_test_output_cpp b/toolkit/components/glean/pytest/pings_test_output_cpp
new file mode 100644
index 0000000000..7f9cc03947
--- /dev/null
+++ b/toolkit/components/glean/pytest/pings_test_output_cpp
@@ -0,0 +1,62 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_Pings_h
+#define mozilla_glean_Pings_h
+
+#include "mozilla/glean/bindings/Ping.h"
+
+namespace mozilla::glean_pings {
+
+/*
+ * Generated from not-baseline.
+ *
+ * This ping is intended to provide metrics that are managed by the library
+ * itself, and not explicitly set by the application or included in the
+ * application's `metrics.yaml` file. The `baseline` ping is automatically sent
+ * when the application is moved to the background.
+ */
+constexpr glean::impl::Ping NotBaseline(1);
+
+/*
+ * Generated from not-metrics.
+ *
+ * The `metrics` ping is intended for all of the metrics that are explicitly set
+ * by the application or are included in the application's `metrics.yaml` file
+ * (except events). The reported data is tied to the ping's *measurement window*,
+ * which is the time between the collection of two `metrics` ping. Ideally, this
+ * window is expected to be about 24 hours, given that the collection is scheduled
+ * daily at 4AM. Data in the `ping_info` section of the ping can be used to infer
+ * the length of this window.
+ */
+constexpr glean::impl::Ping NotMetrics(2);
+
+/*
+ * Generated from not-events.
+ *
+ * The events ping's purpose is to transport all of the event metric information.
+ * The `events` ping is automatically sent when the application is moved to the
+ * background.
+ */
+constexpr glean::impl::Ping NotEvents(3);
+
+/*
+ * Generated from not-deletion-request.
+ *
+ * This ping is submitted when a user opts out of sending technical and
+ * interaction data to Mozilla. This ping is intended to communicate to the Data
+ * Pipeline that the user wishes to have their reported Telemetry data deleted. As
+ * such it attempts to send itself at the moment the user opts out of data
+ * collection.
+ */
+constexpr glean::impl::Ping NotDeletionRequest(4);
+
+
+} // namespace mozilla::glean_pings
+
+#endif // mozilla_glean_Pings_h
diff --git a/toolkit/components/glean/pytest/pings_test_output_js b/toolkit/components/glean/pytest/pings_test_output_js
new file mode 100644
index 0000000000..ecbc6ccf7c
--- /dev/null
+++ b/toolkit/components/glean/pytest/pings_test_output_js
@@ -0,0 +1,99 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GleanJSPingsLookup_h
+#define mozilla_GleanJSPingsLookup_h
+
+#define GLEAN_PING_INDEX_BITS (16)
+#define GLEAN_PING_ID(entry) ((entry) >> GLEAN_PING_INDEX_BITS)
+#define GLEAN_PING_INDEX(entry) ((entry) & ((1UL << GLEAN_PING_INDEX_BITS) - 1))
+
+namespace mozilla::glean {
+
+// Contains the ping id and the index into the ping string table.
+using ping_entry_t = uint32_t;
+
+static Maybe<uint32_t> ping_result_check(const nsACString& aKey, ping_entry_t aEntry);
+
+#if defined(_MSC_VER) && !defined(__clang__)
+const char gPingStringTable[] = {
+#else
+constexpr char gPingStringTable[] = {
+#endif
+ /* 0 - "notBaseline" */ 'n', 'o', 't', 'B', 'a', 's', 'e', 'l', 'i', 'n', 'e', '\0',
+ /* 12 - "notMetrics" */ 'n', 'o', 't', 'M', 'e', 't', 'r', 'i', 'c', 's', '\0',
+ /* 23 - "notEvents" */ 'n', 'o', 't', 'E', 'v', 'e', 'n', 't', 's', '\0',
+ /* 33 - "notDeletionRequest" */ 'n', 'o', 't', 'D', 'e', 'l', 'e', 't', 'i', 'o', 'n', 'R', 'e', 'q', 'u', 'e', 's', 't', '\0',
+};
+
+
+
+const ping_entry_t sPingByNameLookupEntries[] = {
+ 65536,
+ 196631,
+ 262177,
+ 131084
+};
+
+
+
+static Maybe<uint32_t>
+PingByNameLookup(const nsACString& aKey)
+{
+ static const uint8_t BASES[] = {
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+
+
+ const char* bytes = aKey.BeginReading();
+ size_t length = aKey.Length();
+ auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES,
+ sPingByNameLookupEntries);
+ return ping_result_check(aKey, entry);
+}
+
+
+/**
+ * Get a ping's name given its entry from the PHF.
+ */
+static const char* GetPingName(ping_entry_t aEntry) {
+ uint32_t idx = GLEAN_PING_INDEX(aEntry);
+ MOZ_ASSERT(idx < sizeof(gPingStringTable), "Ping index larger than string table");
+ return &gPingStringTable[idx];
+}
+
+/**
+ * Check if the found entry is pointing at the correct ping.
+ * PHF can false-positive a result when the key isn't present, so we check
+ * for a string match. If it fails, return Nothing(). If we found it,
+ * return the ping's id.
+ */
+static Maybe<uint32_t> ping_result_check(const nsACString& aKey, ping_entry_t aEntry) {
+ uint32_t idx = GLEAN_PING_INDEX(aEntry);
+ uint32_t id = GLEAN_PING_ID(aEntry);
+
+ if (MOZ_UNLIKELY(idx > sizeof(gPingStringTable))) {
+ return Nothing();
+ }
+
+ if (aKey.EqualsASCII(&gPingStringTable[idx])) {
+ return Some(id);
+ }
+
+ return Nothing();
+}
+
+#undef GLEAN_PING_INDEX_BITS
+#undef GLEAN_PING_ID
+#undef GLEAN_PING_INDEX
+
+} // namespace mozilla::glean
+#endif // mozilla_GleanJSPingsLookup_h
diff --git a/toolkit/components/glean/pytest/python.ini b/toolkit/components/glean/pytest/python.ini
new file mode 100644
index 0000000000..d91d5bafa2
--- /dev/null
+++ b/toolkit/components/glean/pytest/python.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+subsuite = fog
+
+[test_glean_parser_rust.py]
+[test_glean_parser_cpp.py]
+[test_glean_parser_js.py]
diff --git a/toolkit/components/glean/pytest/test_glean_parser_cpp.py b/toolkit/components/glean/pytest/test_glean_parser_cpp.py
new file mode 100644
index 0000000000..96bcd241a8
--- /dev/null
+++ b/toolkit/components/glean/pytest/test_glean_parser_cpp.py
@@ -0,0 +1,77 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+import io
+import mozunit
+from os import path
+from pathlib import Path
+import sys
+
+
+# Shenanigans to import the cpp outputter extension
+FOG_ROOT_PATH = path.abspath(path.join(path.dirname(__file__), path.pardir))
+sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
+import cpp
+
+# Shenanigans to import the in-tree glean_parser
+GECKO_PATH = path.join(FOG_ROOT_PATH, path.pardir, path.pardir, path.pardir)
+sys.path.append(path.join(GECKO_PATH, "third_party", "python", "glean_parser"))
+from glean_parser import lint, parser, util
+
+
+def test_all_metric_types():
+ """Honestly, this is a pretty bad test.
+ It generates C++ for a given test metrics.yaml and compares it byte-for-byte
+ with an expected output C++ file.
+ Expect it to be fragile.
+ To generate a new expected output file, copy the test yaml over the one in t/c/g,
+ run mach build, then copy the C++ output from objdir/t/c/g/.
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "metrics_test.yaml"))]
+
+ all_objs = parser.parse_objects(input_files, options)
+ assert not util.report_validation_errors(all_objs)
+ assert not lint.lint_metrics(all_objs.value, options)
+
+ output_fd = io.StringIO()
+ cpp.output_cpp(all_objs.value, output_fd, options)
+
+ with open(
+ path.join(path.dirname(__file__), "metrics_test_output_cpp"), "r"
+ ) as file:
+ EXPECTED_CPP = file.read()
+ assert output_fd.getvalue() == EXPECTED_CPP
+
+
+def test_fake_pings():
+ """Another similarly-fragile test.
+ It generates C++ for pings_test.yaml, comparing it byte-for-byte
+ with an expected output C++ file `pings_test_output_cpp`.
+ Expect it to be fragile.
+ To generate a new expected output file, edit t/c/g/metrics_index.py,
+ comment out all other ping yamls, and add one for
+ t/c/g/pytest/pings_test.yaml. Run `mach build` (it'll fail). Copy
+ objdir/t/c/g/GleanPings.h over pings_test_output_cpp.
+ (Don't forget to undo your edits to t/c/g/metrics_index.py)
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "pings_test.yaml"))]
+
+ all_objs = parser.parse_objects(input_files, options)
+ assert not util.report_validation_errors(all_objs)
+ assert not lint.lint_metrics(all_objs.value, options)
+
+ output_fd = io.StringIO()
+ cpp.output_cpp(all_objs.value, output_fd, options)
+
+ with open(path.join(path.dirname(__file__), "pings_test_output_cpp"), "r") as file:
+ EXPECTED_CPP = file.read()
+ assert output_fd.getvalue() == EXPECTED_CPP
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/pytest/test_glean_parser_js.py b/toolkit/components/glean/pytest/test_glean_parser_js.py
new file mode 100644
index 0000000000..09ba623fcf
--- /dev/null
+++ b/toolkit/components/glean/pytest/test_glean_parser_js.py
@@ -0,0 +1,75 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+import io
+import mozunit
+from os import path
+from pathlib import Path
+import sys
+
+
+# Shenanigans to import the js outputter extension
+FOG_ROOT_PATH = path.abspath(path.join(path.dirname(__file__), path.pardir))
+sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
+import js
+
+# Shenanigans to import the in-tree glean_parser
+GECKO_PATH = path.join(FOG_ROOT_PATH, path.pardir, path.pardir, path.pardir)
+sys.path.append(path.join(GECKO_PATH, "third_party", "python", "glean_parser"))
+from glean_parser import lint, parser, util
+
+
+def test_all_metric_types():
+ """Honestly, this is a pretty bad test.
+ It generates C++ for a given test metrics.yaml and compares it byte-for-byte
+ with an expected output C++ file.
+ Expect it to be fragile.
+ To generate a new expected output file, copy the test yaml over the one in t/c/g,
+ run mach build, then copy the C++ output from objdir/t/c/g/.
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "metrics_test.yaml"))]
+
+ all_objs = parser.parse_objects(input_files, options)
+ assert not util.report_validation_errors(all_objs)
+ assert not lint.lint_metrics(all_objs.value, options)
+
+ output_fd = io.StringIO()
+ js.output_js(all_objs.value, output_fd, options)
+
+ with open(path.join(path.dirname(__file__), "metrics_test_output_js"), "r") as file:
+ EXPECTED_JS = file.read()
+ assert output_fd.getvalue() == EXPECTED_JS
+
+
+def test_fake_pings():
+ """Another similarly-fragile test.
+ It generates C++ for pings_test.yaml, comparing it byte-for-byte
+ with an expected output C++ file `pings_test_output_js`.
+ Expect it to be fragile.
+ To generate a new expected output file, edit t/c/g/metrics_index.py,
+ comment out all other ping yamls, and add one for
+ t/c/g/pytest/pings_test.yaml. Run `mach build` (it'll fail). Copy
+ objdir/t/c/g/GleanJSPingsLookup.h over pings_test_output_js.
+ (Don't forget to undo your edits to t/c/g/metrics_index.py)
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "pings_test.yaml"))]
+
+ all_objs = parser.parse_objects(input_files, options)
+ assert not util.report_validation_errors(all_objs)
+ assert not lint.lint_metrics(all_objs.value, options)
+
+ output_fd = io.StringIO()
+ js.output_js(all_objs.value, output_fd, options)
+
+ with open(path.join(path.dirname(__file__), "pings_test_output_js"), "r") as file:
+ EXPECTED_JS = file.read()
+ assert output_fd.getvalue() == EXPECTED_JS
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/pytest/test_glean_parser_rust.py b/toolkit/components/glean/pytest/test_glean_parser_rust.py
new file mode 100644
index 0000000000..6bf3fbe841
--- /dev/null
+++ b/toolkit/components/glean/pytest/test_glean_parser_rust.py
@@ -0,0 +1,113 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+import io
+import mozunit
+from os import path
+from pathlib import Path
+import re
+import sys
+
+
+# Shenanigans to import the rust outputter extension
+FOG_ROOT_PATH = path.abspath(path.join(path.dirname(__file__), path.pardir))
+sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
+import run_glean_parser
+import rust
+
+# Shenanigans to import the in-tree glean_parser
+GECKO_PATH = path.join(FOG_ROOT_PATH, path.pardir, path.pardir, path.pardir)
+sys.path.append(path.join(GECKO_PATH, "third_party", "python", "glean_parser"))
+from glean_parser import lint, parser, util
+
+
+def test_all_metric_types():
+ """Honestly, this is a pretty bad test.
+ It generates Rust for a given test metrics.yaml and compares it byte-for-byte
+ with an expected output Rust file.
+ Expect it to be fragile.
+ To generate a new expected output file, copy the test yaml over the one in t/c/g,
+ run mach build, then copy the rust output from objdir/t/c/g/api/src/.
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "metrics_test.yaml"))]
+
+ all_objs = parser.parse_objects(input_files, options)
+ assert not util.report_validation_errors(all_objs)
+ assert not lint.lint_metrics(all_objs.value, options)
+
+ output_fd = io.StringIO()
+ rust.output_rust(all_objs.value, output_fd, options)
+
+ with open(path.join(path.dirname(__file__), "metrics_test_output"), "r") as file:
+ EXPECTED_RUST = file.read()
+ assert output_fd.getvalue() == EXPECTED_RUST
+
+
+def test_fake_pings():
+ """Another similarly-bad test.
+ It generates Rust for pings_test.yaml, comparing it byte-for-byte
+ with an expected output Rust file.
+ Expect it to be fragile.
+ To generate a new expected output file, copy the test yaml over the one in t/c/g,
+ run mach build, then copy the rust output from objdir/t/c/g/api/src/.
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "pings_test.yaml"))]
+
+ all_objs = parser.parse_objects(input_files, options)
+ assert not util.report_validation_errors(all_objs)
+ assert not lint.lint_metrics(all_objs.value, options)
+
+ output_fd = io.StringIO()
+ rust.output_rust(all_objs.value, output_fd, options)
+
+ with open(path.join(path.dirname(__file__), "pings_test_output"), "r") as file:
+ EXPECTED_RUST = file.read()
+ assert output_fd.getvalue() == EXPECTED_RUST
+
+
+def test_expires_version():
+ """This test relies on the intermediary object format output by glean_parser.
+ Expect it to be fragile on glean_parser updates that change that format.
+ """
+
+ # The test file has 41, 42, 100. Use 42.0a1 here to ensure "expires == version" means expired.
+ options = run_glean_parser.get_parser_options("42.0a1")
+ input_files = [
+ Path(path.join(path.dirname(__file__), "metrics_expires_versions_test.yaml"))
+ ]
+
+ all_objs = parser.parse_objects(input_files, options)
+
+ assert not util.report_validation_errors(all_objs)
+ assert not lint.lint_metrics(all_objs.value, options)
+
+ assert all_objs.value["test"]["expired1"].disabled is True
+ assert all_objs.value["test"]["expired2"].disabled is True
+ assert all_objs.value["test"]["unexpired"].disabled is False
+
+
+def test_numeric_expires():
+ """What if the expires value is a number, not a string?
+ This test relies on the intermediary object format output by glean_parser.
+ Expect it to be fragile on glean_parser updates that change that format.
+ """
+
+ # We'll never get to checking expires values, so this app version shouldn't matter.
+ options = run_glean_parser.get_parser_options("42.0a1")
+ input_files = [
+ Path(path.join(path.dirname(__file__), "metrics_expires_number_test.yaml"))
+ ]
+
+ all_objs = parser.parse_objects(input_files, options)
+ errors = list(all_objs)
+ assert len(errors) == 1
+ assert re.search("99 is not of type 'string'", str(errors[0]))
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/sphinx/glean.py b/toolkit/components/glean/sphinx/glean.py
new file mode 100644
index 0000000000..fb11dacb6e
--- /dev/null
+++ b/toolkit/components/glean/sphinx/glean.py
@@ -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 http://mozilla.org/MPL/2.0/.
+
+import os
+from pathlib import Path
+import sys
+
+
+def setup(app):
+ from moztreedocs import manager
+
+ # Import the list of metrics and ping files
+ glean_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))
+ sys.path.append(glean_dir)
+ from metrics_index import metrics_yamls, pings_yamls
+
+ # Import the custom version expiry code.
+ glean_parser_ext_dir = os.path.abspath(
+ Path(glean_dir) / "build_scripts" / "glean_parser_ext"
+ )
+ sys.path.append(glean_parser_ext_dir)
+ from run_glean_parser import get_parser_options
+
+ firefox_version = "4.0a1" # TODO: bug 1676416 - Get the real app version.
+ parser_config = get_parser_options(firefox_version)
+
+ input_files = [Path(os.path.join(manager.topsrcdir, x)) for x in metrics_yamls]
+ input_files += [Path(os.path.join(manager.topsrcdir, x)) for x in pings_yamls]
+
+ # Generate the autodocs.
+ from glean_parser import translate
+
+ out_path = Path(os.path.join(manager.staging_dir, "metrics"))
+ translate.translate(
+ input_files, "markdown", out_path, {"project_title": "Firefox"}, parser_config
+ )
+
+ # Rename the generated docfile to index so Sphinx finds it
+ os.rename(os.path.join(out_path, "metrics.md"), os.path.join(out_path, "index.md"))
diff --git a/toolkit/components/glean/src/lib.rs b/toolkit/components/glean/src/lib.rs
new file mode 100644
index 0000000000..11bf1dd58c
--- /dev/null
+++ b/toolkit/components/glean/src/lib.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/.
+
+//! Firefox on Glean (FOG) is the name of the layer that integrates the [Glean SDK][glean-sdk] into Firefox Desktop.
+//! It is currently being designed and implemented.
+//!
+//! The [Glean SDK][glean-sdk] is a data collection library built by Mozilla for use in its products.
+//! Like [Telemetry][telemetry], it can be used to
+//! (in accordance with our [Privacy Policy][privacy-policy])
+//! send anonymous usage statistics to Mozilla in order to make better decisions.
+//!
+//! Documentation can be found online in the [Firefox Source Docs][docs].
+//!
+//! [glean-sdk]: https://github.com/mozilla/glean/
+//! [book-of-glean]: https://mozilla.github.io/glean/book/index.html
+//! [privacy-policy]: https://www.mozilla.org/privacy/
+//! [docs]: https://firefox-source-docs.mozilla.org/toolkit/components/glean/
+
+// No one is currently using the Glean SDK, so let's export it, so we know it gets
+// compiled.
+pub extern crate fog;
+
+#[macro_use]
+extern crate cstr;
+#[macro_use]
+extern crate xpcom;
+
+use std::ffi::CStr;
+use std::os::raw::c_char;
+
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_OK};
+use nsstring::{nsACString, nsAString, nsCStr, nsCString, nsStr, nsString};
+use xpcom::interfaces::{
+ mozIViaduct, nsIFile, nsIObserver, nsIPrefBranch, nsIPropertyBag2, nsISupports, nsIXULAppInfo,
+};
+use xpcom::{RefPtr, XpCom};
+
+use glean::{ClientInfoMetrics, Configuration};
+
+mod viaduct_uploader;
+
+use crate::viaduct_uploader::ViaductUploader;
+
+/// Project FOG's entry point.
+///
+/// This assembles client information and the Glean configuration and then initializes the global
+/// Glean instance.
+#[no_mangle]
+pub unsafe extern "C" fn fog_init() -> nsresult {
+ fog::metrics::fog::initialization.start();
+
+ log::debug!("Initializing FOG.");
+
+ let data_path = match get_data_path() {
+ Ok(dp) => dp,
+ Err(e) => return e,
+ };
+
+ let (app_build, app_display_version, channel) = match get_app_info() {
+ Ok(ai) => ai,
+ Err(e) => return e,
+ };
+
+ let (os_version, _architecture) = match get_system_info() {
+ Ok(si) => si,
+ Err(e) => return e,
+ };
+
+ fog::metrics::fog_validation::os_version.set(os_version);
+
+ let client_info = ClientInfoMetrics {
+ app_build,
+ app_display_version,
+ };
+ log::debug!("Client Info: {:#?}", client_info);
+
+ let pref_observer = UploadPrefObserver::allocate(InitUploadPrefObserver {});
+ if let Err(e) = pref_observer.begin_observing() {
+ log::error!(
+ "Could not observe data upload pref. Abandoning FOG init due to {:?}",
+ e
+ );
+ return e;
+ }
+
+ const SERVER: &str = "https://incoming.telemetry.mozilla.org";
+ let localhost_port = static_prefs::pref!("telemetry.fog.test.localhost_port");
+ let server = if localhost_port > 0 {
+ format!("http://localhost:{}", localhost_port)
+ } else {
+ String::from(SERVER)
+ };
+
+ let upload_enabled = static_prefs::pref!("datareporting.healthreport.uploadEnabled");
+ let data_path = data_path.to_string();
+ let configuration = Configuration {
+ upload_enabled,
+ data_path,
+ application_id: "firefox.desktop".to_string(),
+ max_events: None,
+ delay_ping_lifetime_io: false,
+ channel: Some(channel),
+ server_endpoint: Some(server),
+ uploader: Some(Box::new(crate::ViaductUploader) as Box<dyn glean::net::PingUploader>),
+ };
+
+ log::debug!("Configuration: {:#?}", configuration);
+
+ // Ensure Viaduct is initialized for networking unconditionally so we don't
+ // need to check again if upload is later enabled.
+ if let Some(viaduct) =
+ xpcom::create_instance::<mozIViaduct>(cstr!("@mozilla.org/toolkit/viaduct;1"))
+ {
+ let result = viaduct.EnsureInitialized();
+ if result.failed() {
+ log::error!("Failed to ensure viaduct was initialized due to {}. Ping upload may not be available.", result.error_name());
+ }
+ } else {
+ log::error!("Failed to create Viaduct via XPCOM. Ping upload may not be available.");
+ }
+
+ if configuration.data_path.len() > 0 {
+ glean::initialize(configuration, client_info);
+
+ // Register all custom pings before we initialize.
+ fog::pings::register_pings();
+
+ fog::metrics::fog::initialization.stop();
+ schedule_fog_validation_ping();
+ }
+
+ NS_OK
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn fog_shutdown() {
+ glean::shutdown();
+}
+
+/// Construct and return the data_path from the profile dir, or return an error.
+fn get_data_path() -> Result<String, nsresult> {
+ let dir_svc = match xpcom::services::get_DirectoryService() {
+ Some(ds) => ds,
+ _ => return Err(NS_ERROR_FAILURE),
+ };
+ let mut profile_dir = xpcom::GetterAddrefs::<nsIFile>::new();
+ unsafe {
+ dir_svc
+ .Get(
+ cstr!("ProfD").as_ptr(),
+ &nsIFile::IID,
+ profile_dir.void_ptr(),
+ )
+ .to_result()?;
+ }
+ let profile_dir = profile_dir.refptr().ok_or(NS_ERROR_FAILURE)?;
+ let mut profile_path = nsString::new();
+ unsafe {
+ (*profile_dir).GetPath(&mut *profile_path).to_result()?;
+ }
+ let profile_path = String::from_utf16(&profile_path[..]).map_err(|_| NS_ERROR_FAILURE)?;
+ let data_path = profile_path + "/datareporting/glean";
+ Ok(data_path)
+}
+
+/// Return a tuple of the build_id, app version, and build channel.
+/// If the XUL Runtime isn't a XULAppInfo (e.g. in xpcshell),
+/// build_id ad app_version will be "unknown".
+/// Other problems result in an error being returned instead.
+fn get_app_info() -> Result<(String, String, String), nsresult> {
+ let xul = xpcom::services::get_XULRuntime().ok_or(NS_ERROR_FAILURE)?;
+
+ let mut channel = nsCString::new();
+ unsafe {
+ xul.GetDefaultUpdateChannel(&mut *channel).to_result()?;
+ }
+
+ let app_info = match xul.query_interface::<nsIXULAppInfo>() {
+ Some(ai) => ai,
+ // In e.g. xpcshell the XULRuntime isn't XULAppInfo.
+ // We still want to return sensible values so tests don't explode.
+ _ => {
+ return Ok((
+ "unknown".to_owned(),
+ "unknown".to_owned(),
+ channel.to_string(),
+ ))
+ }
+ };
+
+ let mut build_id = nsCString::new();
+ unsafe {
+ app_info.GetAppBuildID(&mut *build_id).to_result()?;
+ }
+
+ let mut version = nsCString::new();
+ unsafe {
+ app_info.GetVersion(&mut *version).to_result()?;
+ }
+
+ Ok((
+ build_id.to_string(),
+ version.to_string(),
+ channel.to_string(),
+ ))
+}
+
+/// Return a tuple of os_version and architecture, or an error.
+fn get_system_info() -> Result<(String, String), nsresult> {
+ let info_service = xpcom::get_service::<nsIPropertyBag2>(cstr!("@mozilla.org/system-info;1"))
+ .ok_or(NS_ERROR_FAILURE)?;
+
+ let os_version_key: Vec<u16> = "version".encode_utf16().collect();
+ let os_version_key = &nsStr::from(&os_version_key) as &nsAString;
+ let mut os_version = nsCString::new();
+ unsafe {
+ info_service
+ .GetPropertyAsACString(os_version_key, &mut *os_version)
+ .to_result()?;
+ }
+
+ let arch_key: Vec<u16> = "arch".encode_utf16().collect();
+ let arch_key = &nsStr::from(&arch_key) as &nsAString;
+ let mut arch = nsCString::new();
+ unsafe {
+ info_service
+ .GetPropertyAsACString(arch_key, &mut *arch)
+ .to_result()?;
+ }
+
+ Ok((os_version.to_string(), arch.to_string()))
+}
+
+// Partially cargo-culted from https://searchfox.org/mozilla-central/rev/598e50d2c3cd81cd616654f16af811adceb08f9f/security/manager/ssl/cert_storage/src/lib.rs#1192
+#[derive(xpcom)]
+#[xpimplements(nsIObserver)]
+#[refcnt = "atomic"]
+struct InitUploadPrefObserver {}
+
+#[allow(non_snake_case)]
+impl UploadPrefObserver {
+ unsafe fn begin_observing(&self) -> Result<(), nsresult> {
+ let pref_service = xpcom::services::get_PrefService().ok_or(NS_ERROR_FAILURE)?;
+ let pref_branch: RefPtr<nsIPrefBranch> =
+ (*pref_service).query_interface().ok_or(NS_ERROR_FAILURE)?;
+ let pref_nscstr = &nsCStr::from("datareporting.healthreport.uploadEnabled") as &nsACString;
+ (*pref_branch)
+ .AddObserverImpl(pref_nscstr, self.coerce::<nsIObserver>(), false)
+ .to_result()?;
+ Ok(())
+ }
+
+ unsafe fn Observe(
+ &self,
+ _subject: *const nsISupports,
+ topic: *const c_char,
+ pref_name: *const i16,
+ ) -> nserror::nsresult {
+ let topic = CStr::from_ptr(topic).to_str().unwrap();
+ // Conversion utf16 to utf8 is messy.
+ // We should only ever observe changes to the one pref we want,
+ // but just to be on the safe side let's assert.
+
+ // cargo-culted from https://searchfox.org/mozilla-central/rev/598e50d2c3cd81cd616654f16af811adceb08f9f/security/manager/ssl/cert_storage/src/lib.rs#1606-1612
+ // (with a little transformation)
+ let len = (0..).take_while(|&i| *pref_name.offset(i) != 0).count(); // find NUL.
+ let slice = std::slice::from_raw_parts(pref_name as *const u16, len);
+ let pref_name = match String::from_utf16(slice) {
+ Ok(name) => name,
+ Err(_) => return NS_ERROR_FAILURE,
+ };
+ log::info!("Observed {:?}, {:?}", topic, pref_name);
+ debug_assert!(
+ topic == "nsPref:changed" && pref_name == "datareporting.healthreport.uploadEnabled"
+ );
+
+ let upload_enabled = static_prefs::pref!("datareporting.healthreport.uploadEnabled");
+ glean::set_upload_enabled(upload_enabled);
+ NS_OK
+ }
+}
+
+static mut PENDING_BUF: Vec<u8> = Vec::new();
+
+// IPC serialization/deserialization methods
+// Crucially important that the first two not be called on multiple threads.
+
+/// Only safe if only called on a single thread (the same single thread you call
+/// fog_give_ipc_buf on).
+#[no_mangle]
+pub unsafe extern "C" fn fog_serialize_ipc_buf() -> usize {
+ if let Some(buf) = fog::ipc::take_buf() {
+ PENDING_BUF = buf;
+ PENDING_BUF.len()
+ } else {
+ PENDING_BUF = vec![];
+ 0
+ }
+}
+
+#[no_mangle]
+/// Only safe if called on a single thread (the same single thread you call
+/// fog_serialize_ipc_buf on), and if buf points to an allocated buffer of at
+/// least buf_len bytes.
+pub unsafe extern "C" fn fog_give_ipc_buf(buf: *mut u8, buf_len: usize) -> usize {
+ let pending_len = PENDING_BUF.len();
+ if buf.is_null() || buf_len < pending_len {
+ return 0;
+ }
+ std::ptr::copy_nonoverlapping(PENDING_BUF.as_ptr(), buf, pending_len);
+ PENDING_BUF = Vec::new();
+ pending_len
+}
+
+#[no_mangle]
+/// Only safe if buf points to an allocated buffer of at least buf_len bytes.
+/// No ownership is transfered to Rust by this method: caller owns the memory at
+/// buf before and after this call.
+pub unsafe extern "C" fn fog_use_ipc_buf(buf: *const u8, buf_len: usize) {
+ let slice = std::slice::from_raw_parts(buf, buf_len);
+ let res = fog::ipc::replay_from_buf(slice);
+ if res.is_err() {
+ log::warn!("Unable to replay ipc buffer. This will result in data loss.");
+ fog::metrics::fog_ipc::replay_failures.add(1);
+ }
+}
+
+#[no_mangle]
+/// Sets the debug tag for pings assembled in the future.
+/// Returns an error result if the provided value is not a valid tag.
+pub unsafe extern "C" fn fog_set_debug_view_tag(value: &nsACString) -> nsresult {
+ let result = glean::set_debug_view_tag(&value.to_string());
+ if result {
+ return NS_OK;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+}
+
+#[no_mangle]
+/// Submits a ping by name.
+pub unsafe extern "C" fn fog_submit_ping(ping_name: &nsACString) -> nsresult {
+ glean::submit_ping_by_name(&ping_name.to_string(), None);
+ NS_OK
+}
+
+#[no_mangle]
+/// Turns ping logging on or off.
+/// Returns an error if the logging failed to be configured.
+pub unsafe extern "C" fn fog_set_log_pings(value: bool) -> nsresult {
+ glean::set_log_pings(value);
+ NS_OK
+}
+
+fn schedule_fog_validation_ping() {
+ std::thread::spawn(|| {
+ loop {
+ // Sleep for an hour before and between submissions.
+ std::thread::sleep(std::time::Duration::from_secs(60 * 60));
+ fog::pings::fog_validation.submit(None);
+ }
+ });
+}
diff --git a/toolkit/components/glean/src/viaduct_uploader.rs b/toolkit/components/glean/src/viaduct_uploader.rs
new file mode 100644
index 0000000000..3d01441cb4
--- /dev/null
+++ b/toolkit/components/glean/src/viaduct_uploader.rs
@@ -0,0 +1,54 @@
+// 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 glean::net::{PingUploader, UploadResult};
+use url::Url;
+use viaduct::Request;
+
+/// An uploader that uses [Viaduct](https://github.com/mozilla/application-services/tree/main/components/viaduct).
+#[derive(Debug)]
+pub(crate) struct ViaductUploader;
+
+impl PingUploader for ViaductUploader {
+ /// Uploads a ping to a server.
+ ///
+ /// # Arguments
+ ///
+ /// * `url` - the URL path to upload the data to.
+ /// * `body` - the serialized text data to send.
+ /// * `headers` - a vector of tuples containing the headers to send with
+ /// the request, i.e. (Name, Value).
+ fn upload(&self, url: String, body: Vec<u8>, headers: Vec<(String, String)>) -> UploadResult {
+ log::trace!("FOG Ping Uploader uploading to {}", url);
+ let url_clone = url.clone();
+ let result: std::result::Result<UploadResult, viaduct::Error> = (move || {
+ let localhost_port = static_prefs::pref!("telemetry.fog.test.localhost_port");
+ if localhost_port < 0 {
+ log::info!("FOG Ping uploader faking success");
+ return Ok(UploadResult::HttpStatus(200));
+ }
+ let parsed_url = Url::parse(&url_clone)?;
+
+ log::info!("FOG Ping uploader uploading to {:?}", parsed_url);
+
+ let mut req = Request::post(parsed_url.clone()).body(body.clone());
+ for (header_key, header_value) in &headers {
+ req = req.header(header_key.to_owned(), header_value)?;
+ }
+
+ log::trace!("FOG Ping Uploader sending ping to {}", parsed_url);
+ let res = req.send()?;
+ Ok(UploadResult::HttpStatus(res.status.into()))
+ })();
+ log::trace!(
+ "FOG Ping Uploader completed uploading to {} (Result {:?})",
+ url,
+ result
+ );
+ match result {
+ Ok(result) => result,
+ _ => UploadResult::UnrecoverableFailure,
+ }
+ }
+}
diff --git a/toolkit/components/glean/test_metrics.yaml b/toolkit/components/glean/test_metrics.yaml
new file mode 100644
index 0000000000..db79622fcd
--- /dev/null
+++ b/toolkit/components/glean/test_metrics.yaml
@@ -0,0 +1,353 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+# This file defines the metrics that are recorded by the Glean SDK. They are
+# automatically converted to platform-specific code at build time using the
+# `glean_parser` PyPI package.
+
+# This file is for Internal FOG Test Use Only.
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0
+
+test_only:
+ bad_code:
+ type: counter
+ description: |
+ Number of times we encountered bad code.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ can_we_time_it:
+ type: timespan
+ time_unit: nanosecond
+ description: |
+ Test metric for a timespan.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ cheesy_string:
+ type: string
+ description: |
+ Only the cheesiest of strings.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673662
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673662#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ cheesy_string_list:
+ type: string_list
+ description: |
+ Only the cheesiest of strings. In list form!
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1682960
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1682960#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ what_a_date:
+ type: datetime
+ time_unit: second
+ description: >
+ ...To be writing FOG code.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ what_id_it: # Using a different metrics yaml style for fun.
+ type: uuid
+ description: |
+ Just a UUID.
+ This is a test-only metric.
+ bugs: ["https://bugzilla.mozilla.org/show_bug.cgi?id=1673664"]
+ data_reviews: ["https://bugzilla.mozilla.org/show_bug.cgi?id=1673664#c1"]
+ data_sensitivity: ["technical"]
+ notification_emails: ["glean-team@mozilla.com"]
+ expires: never
+ send_in_pings: ["test-ping"]
+
+ can_we_flag_it:
+ type: boolean
+ description: |
+ Test metric for a boolean.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ do_you_remember:
+ type: memory_distribution
+ memory_unit: megabyte
+ description: |
+ They say it's the second thing to go.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673648
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673648#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+
+ what_time_is_it:
+ type: timing_distribution
+ time_unit: microsecond
+ description: |
+ Adheres to at least two of the top ten fallacies programmers believe
+ about time.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673663
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673663#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+
+test_only.ipc:
+ a_counter:
+ type: counter
+ description: |
+ This is a test-only metric.
+ Just counting things.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ a_bool:
+ type: boolean
+ description: |
+ This is a test-only metric.
+ Just flagging things.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ a_date:
+ type: datetime
+ time_unit: second
+ description: |
+ This is a test-only metric.
+ Just putting things on the calendar.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ a_string:
+ type: string
+ description: |
+ This is a test-only metric.
+ Just setting some strings.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ a_memory_dist:
+ type: memory_distribution
+ memory_unit: kilobyte
+ description: |
+ This is a test-only metric.
+ Just measuring memory.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ a_timing_dist:
+ type: timing_distribution
+ description: |
+ This is a test-only metric.
+ Just measuring time.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ a_string_list:
+ type: string_list
+ description: |
+ This is a test-only metric.
+ Just appending some strings.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ an_event:
+ type: event
+ extra_keys:
+ extra1:
+ description: "Some extra data"
+ extra2:
+ description: "Some extra data again"
+ description: |
+ This is a test-only metric.
+ Just recording some events.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ no_extra_event:
+ type: event
+ description: |
+ This is a test-only metric.
+ Just recording some events without the extra fuss.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ a_uuid:
+ type: uuid
+ description: |
+ This is a test-only metric.
+ Just recording some unique identifiers.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
diff --git a/toolkit/components/glean/test_pings.yaml b/toolkit/components/glean/test_pings.yaml
new file mode 100644
index 0000000000..dc65c17c5c
--- /dev/null
+++ b/toolkit/components/glean/test_pings.yaml
@@ -0,0 +1,25 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+# This file defines the pings that are recorded by the Glean SDK. They are
+# automatically converted to platform-specific code at build time using the
+# `glean_parser` PyPI package.
+
+# This file is presently for Internal FOG Test Use Only.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/1-0-0
+
+one-ping-only:
+ description: |
+ This ping is for tests only.
+ include_client_id: false
+ send_if_empty: true
+ bugs:
+ - https://bugzilla.mozilla.org/1673660
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673660#c1
+ notification_emails:
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
diff --git a/toolkit/components/glean/xpcom/FOG.cpp b/toolkit/components/glean/xpcom/FOG.cpp
new file mode 100644
index 0000000000..fd530766eb
--- /dev/null
+++ b/toolkit/components/glean/xpcom/FOG.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFOG.h"
+#include "mozilla/FOG.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla {
+
+static StaticRefPtr<FOG> gFOG;
+
+// static
+already_AddRefed<FOG> FOG::GetSingleton() {
+ if (gFOG) {
+ return do_AddRef(gFOG);
+ }
+
+ gFOG = new FOG();
+ RunOnShutdown([&] {
+ gFOG->Shutdown();
+ gFOG = nullptr;
+ });
+ return do_AddRef(gFOG);
+}
+
+void FOG::Shutdown() { glean::impl::fog_shutdown(); }
+
+NS_IMETHODIMP
+FOG::InitializeFOG() { return glean::impl::fog_init(); }
+
+NS_IMETHODIMP
+FOG::SetLogPings(bool aEnableLogPings) {
+ return glean::impl::fog_set_log_pings(aEnableLogPings);
+}
+
+NS_IMETHODIMP
+FOG::SetTagPings(const nsACString& aDebugTag) {
+ return glean::impl::fog_set_debug_view_tag(&aDebugTag);
+}
+
+NS_IMETHODIMP
+FOG::SendPing(const nsACString& aPingName) {
+ return glean::impl::fog_submit_ping(&aPingName);
+}
+
+NS_IMPL_ISUPPORTS(FOG, nsIFOG)
+
+} // namespace mozilla
diff --git a/toolkit/components/glean/xpcom/FOG.h b/toolkit/components/glean/xpcom/FOG.h
new file mode 100644
index 0000000000..508865ad41
--- /dev/null
+++ b/toolkit/components/glean/xpcom/FOG.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_FOG_h
+#define mozilla_FOG_h
+
+#include "nsIFOG.h"
+
+namespace mozilla {
+class FOG final : public nsIFOG {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIFOG
+
+ public:
+ FOG() = default;
+ static already_AddRefed<FOG> GetSingleton();
+
+ private:
+ ~FOG() = default;
+ void Shutdown();
+};
+
+}; // namespace mozilla
+
+#endif // mozilla_FOG_h
diff --git a/toolkit/components/glean/xpcom/components.conf b/toolkit/components/glean/xpcom/components.conf
new file mode 100644
index 0000000000..db87b2f6a8
--- /dev/null
+++ b/toolkit/components/glean/xpcom/components.conf
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{98d0e975-9cad-4ce3-ae2f-f878b8be6307}',
+ 'contract_ids': ['@mozilla.org/toolkit/glean;1'],
+ 'singleton': True,
+ 'type': 'mozilla::FOG',
+ 'headers': ['mozilla/FOG.h'],
+ 'constructor': 'mozilla::FOG::GetSingleton',
+ }
+]
diff --git a/toolkit/components/glean/xpcom/moz.build b/toolkit/components/glean/xpcom/moz.build
new file mode 100644
index 0000000000..cc4dd1496d
--- /dev/null
+++ b/toolkit/components/glean/xpcom/moz.build
@@ -0,0 +1,28 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+if CONFIG["MOZ_GLEAN"]:
+ FINAL_LIBRARY = "xul"
+
+ EXPORTS.mozilla += [
+ "FOG.h",
+ ]
+
+ UNIFIED_SOURCES += [
+ "FOG.cpp",
+ ]
+
+ XPCOM_MANIFESTS += ["components.conf"]
+
+ XPIDL_MODULE = "fog"
+
+ XPIDL_SOURCES += [
+ "nsIFOG.idl",
+ "nsIGleanMetrics.idl",
+ ]
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Telemetry")
diff --git a/toolkit/components/glean/xpcom/nsIFOG.idl b/toolkit/components/glean/xpcom/nsIFOG.idl
new file mode 100644
index 0000000000..80112c6a17
--- /dev/null
+++ b/toolkit/components/glean/xpcom/nsIFOG.idl
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(98d0e975-9cad-4ce3-ae2f-f878b8be6307)]
+interface nsIFOG : nsISupports
+{
+
+ /**
+ * Initialize FOG.
+ *
+ * To be scheduled at some opportune time after the bulk of Firefox startup
+ * has completed.
+ */
+ void initializeFOG();
+
+ /**
+ * Enable or Disable the logging of pings in the Glean SDK.
+ * See https://firefox-source-docs.mozilla.org/toolkit/components/glean/testing.html
+ * for details.
+ *
+ * @param aEnableLogPings - true to enable logging, false to disable.
+ */
+ void setLogPings(in boolean aEnableLogPings);
+
+ /**
+ * Set the tag to be applied to pings assembled from now on.
+ * See https://firefox-source-docs.mozilla.org/toolkit/components/glean/testing.html
+ * for details.
+ *
+ * @param aDebugTag - The string tag to apply.
+ * If it cannot be applied (e.g it contains characters that are
+ * forbidden in HTTP headers) the old value will remain.
+ */
+ void setTagPings(in ACString aDebugTag);
+
+ /**
+ * Send the named ping.
+ * See https://firefox-source-docs.mozilla.org/toolkit/components/glean/testing.html
+ * for details.
+ *
+ * @param aPingName - The name of the ping to send. If no ping of that name
+ * exists, or the ping is known but cannot be assembled
+ * (e.g if it is empty), no ping will be sent.
+ */
+ void sendPing(in ACString aPingName);
+};
diff --git a/toolkit/components/glean/xpcom/nsIGleanMetrics.idl b/toolkit/components/glean/xpcom/nsIGleanMetrics.idl
new file mode 100644
index 0000000000..a602200f2f
--- /dev/null
+++ b/toolkit/components/glean/xpcom/nsIGleanMetrics.idl
@@ -0,0 +1,394 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(d3180fe0-19fa-11eb-8b6f-0800200c9a66)]
+interface nsIGleanBoolean : nsISupports
+{
+ /**
+ * Set to the specified boolean value.
+ *
+ * @param value the value to set.
+ */
+ void set(in bool value);
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a boolean.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or undefined if there is no value.
+ */
+ jsval testGetValue([optional] in AUTF8String aPingName);
+};
+
+[scriptable, uuid(aa15fd20-1e8a-11eb-9bec-0800200c9a66)]
+interface nsIGleanDatetime : nsISupports
+{
+ /**
+ * Set the datetime to the provided value, or the local now.
+ *
+ * @param aValue The time value in milliseconds since epoch. Defaults to local now.
+ */
+ [optional_argc]
+ void set([optional] in PRTime aValue);
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or undefined if there is no value.
+ */
+ [implicit_jscontext]
+ jsval testGetValue([optional] in AUTF8String aPingName);
+};
+
+[scriptable, uuid(05b89d2a-d57c-11ea-82da-3f63399a6f5a)]
+interface nsIGleanCounter : nsISupports
+{
+ /*
+ * Increases the counter by `amount`.
+ *
+ * @param amount The amount to increase by. Should be positive.
+ */
+ void add(in uint32_t amount);
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or undefined if there is no value.
+ */
+ jsval testGetValue([optional] in AUTF8String aPingName);
+};
+
+[scriptable, uuid(92e14730-9b5f-45a1-b018-f588d0b964d8)]
+interface nsIGleanTimingDistribution : nsISupports
+{
+ /**
+ * Starts tracking time for the provided metric.
+ *
+ * @returns A unique timer id for the new timer
+ */
+ [implicit_jscontext]
+ jsval start();
+
+ /**
+ * Stops tracking time for the provided metric and timer id.
+ *
+ * Adds a count to the corresponding bucket in the timing distribution.
+ * This will record an error if no `start` was called for this TimerId or
+ * if this TimerId was used to call `cancel`.
+ *
+ * @param aId The TimerId associated with this timing. This allows for
+ * concurrent timing of events associated with different ids.
+ */
+ void stopAndAccumulate(in uint64_t aId);
+
+ /**
+ * Aborts a previous `start` call. No error is recorded if no `start` was
+ * called. (But then where did you get that id from?)
+ *
+ * @param aId The TimerID whose `start` you wish to abort.
+ */
+ void cancel(in uint64_t aId);
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a DistributionData.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ [implicit_jscontext]
+ jsval testGetValue([optional] in ACString aPingName);
+};
+
+[scriptable, uuid(eea5ed46-16ba-46cd-bb1f-504581987fe1)]
+interface nsIGleanMemoryDistribution : nsISupports
+{
+ /*
+ * Accumulates the provided sample in the metric.
+ *
+ * @param aSample The sample to be recorded by the metric. The sample is
+ * assumed to be in the confgured memory unit of the metric.
+ *
+ * Notes: Values bigger than 1 Terabyte (2^40 bytes) are truncated and an
+ * InvalidValue error is recorded.
+ */
+ void accumulate(in uint64_t aSample);
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a DistributionData.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or Nothing() if there is no value.
+ */
+ [implicit_jscontext]
+ jsval testGetValue([optional] in ACString aPingName);
+};
+
+[scriptable, uuid(5223a48b-687d-47ff-a629-fd4a72d1ecfa)]
+interface nsIGleanPing : nsISupports
+{
+ /**
+ * Collect and submit the ping for eventual upload.
+ *
+ * This will collect all stored data to be included in the ping.
+ * Data with lifetime `ping` will then be reset.
+ *
+ * If the ping is configured with `send_if_empty = false`
+ * and the ping currently contains no content,
+ * it will not be queued for upload.
+ * If the ping is configured with `send_if_empty = true`
+ * it will be queued for upload even if empty.
+ *
+ * Pings always contain the `ping_info` and `client_info` sections.
+ * See [ping sections](https://mozilla.github.io/glean/book/user/pings/index.html#ping-sections)
+ * for details.
+ *
+ * @param aReason - Optional. The reason the ping is being submitted.
+ * Must match one of the configured `reason_codes`.
+ */
+ void submit([optional] in ACString aReason);
+};
+
+[scriptable, uuid(d84a3555-46f1-48c1-9122-e8e88b069d2b)]
+interface nsIGleanString : nsISupports
+{
+ /*
+ * Set to the specified value.
+ *
+ * @param value The string to set the metric to.
+ */
+ void set(in AUTF8String value);
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as a string.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or undefined if there is no value.
+ */
+ [implicit_jscontext]
+ jsval testGetValue([optional] in AUTF8String aPingName);
+};
+
+[scriptable, uuid(46751205-2ac7-47dc-91d2-ef4a95ef2af9)]
+interface nsIGleanStringList : nsISupports
+{
+ /**
+ * Adds a new string to the list.
+ *
+ * Truncates the value and logs an error if it is longer than 50 bytes.
+ *
+ * @param value The string to add.
+ */
+ void add(in AUTF8String value);
+
+ /**
+ * Sets to a specific list of strings.
+ *
+ * Truncates the list and logs an error if longer than 20 items.
+ * Truncates any item longer than 50 bytes and logs an error.
+ *
+ * @param value The list of strings to set.
+ */
+ void set(in Array<AUTF8String> value);
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or undefined if there is no value.
+ */
+ [implicit_jscontext]
+ jsval testGetValue([optional] in AUTF8String aPingName);
+};
+
+[scriptable, uuid(2586530c-030f-11eb-93cb-cbf30d25225a)]
+interface nsIGleanTimespan : nsISupports
+{
+ /**
+ * Start tracking time for the provided metric.
+ *
+ * This records an error if it’s already tracking time (i.e. start was already
+ * called with no corresponding [stop]): in that case the original
+ * start time will be preserved.
+ */
+ void start();
+
+ /**
+ * Stop tracking time for the provided metric.
+ *
+ * Sets the metric to the elapsed time, but does not overwrite an already
+ * existing value.
+ * This will record an error if no [start] was called or there is an already
+ * existing value.
+ */
+ void stop();
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or undefined if there is no value.
+ */
+ jsval testGetValue([optional] in AUTF8String aPingName);
+};
+
+[scriptable, uuid(395700e7-06f6-46be-adcc-ea58977fda6d)]
+interface nsIGleanUuid : nsISupports
+{
+ /**
+ * Set to the specified value.
+ *
+ * @param aValue The UUID to set the metric to.
+ */
+ void set(in AUTF8String aValue);
+
+ /**
+ * Generate a new random UUID and set the metric to it.
+ */
+ void generateAndSet();
+
+ /**
+ * **Test-only API**
+ *
+ * Gets the currently stored value as an integer.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric, or undefined if there is no value.
+ */
+ [implicit_jscontext]
+ jsval testGetValue([optional] in AUTF8String aPingName);
+};
+
+[scriptable, uuid(1b01424a-1f55-11eb-92a5-0754f6c3f240)]
+interface nsIGleanEvent : nsISupports
+{
+ /*
+ * Record an event.
+ *
+ * @param aExtra An (optional) map of extra values.
+ */
+ [implicit_jscontext]
+ void record([optional] in jsval aExtra);
+
+ /**
+ * **Test-only API**
+ *
+ * Get a list of currently stored events for this event metric.
+ *
+ * This function will attempt to await the last parent-process task (if any)
+ * writing to the the metric's storage engine before returning a value.
+ * This function will not wait for data from child processes.
+ *
+ * This doesn't clear the stored value.
+ * Parent process only. Panics in child processes.
+ *
+ * @param aPingName The (optional) name of the ping to retrieve the metric
+ * for. Defaults to the first value in `send_in_pings`.
+ *
+ * @return value of the stored metric.
+ */
+ [implicit_jscontext]
+ jsval testGetValue([optional] in AUTF8String aPingName);
+};
diff --git a/toolkit/components/glean/xpcshell/test_Glean.js b/toolkit/components/glean/xpcshell/test_Glean.js
new file mode 100644
index 0000000000..de273634a3
--- /dev/null
+++ b/toolkit/components/glean/xpcshell/test_Glean.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* FIXME: Remove these global markers.
+ * FOG doesn't follow the stricter naming patterns as expected by tool configuration yet.
+ * See https://searchfox.org/mozilla-central/source/.eslintrc.js#24
+ * Reorganizing the directory structure will take this into account.
+ */
+/* global add_task, Assert, do_get_profile */
+"use strict";
+
+Cu.importGlobalProperties(["Glean", "GleanPings"]);
+const { MockRegistrar } = ChromeUtils.import(
+ "resource://testing-common/MockRegistrar.jsm"
+);
+const { ObjectUtils } = ChromeUtils.import(
+ "resource://gre/modules/ObjectUtils.jsm"
+);
+const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+/**
+ * Mock the SysInfo object used to read System data in Gecko.
+ */
+var SysInfo = {
+ overrides: {},
+
+ /**
+ * Checks if overrides are present and return them.
+ *
+ * @returns the overridden value or undefined if not present.
+ */
+ _getOverridden(name) {
+ if (name in this.overrides) {
+ return this.overrides[name];
+ }
+
+ return undefined;
+ },
+
+ // To support nsIPropertyBag.
+ getProperty(name) {
+ let override = this._getOverridden(name);
+ return override !== undefined
+ ? override
+ : this._genuine.QueryInterface(Ci.nsIPropertyBag).getProperty(name);
+ },
+
+ // To support nsIPropertyBag2.
+ get(name) {
+ let override = this._getOverridden(name);
+ return override !== undefined
+ ? override
+ : this._genuine.QueryInterface(Ci.nsIPropertyBag2).get(name);
+ },
+
+ // To support nsIPropertyBag2.
+ getPropertyAsACString(name) {
+ return this.get(name);
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIPropertyBag2", "nsISystemInfo"]),
+};
+
+function sleep(ms) {
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+add_task(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // Mock SysInfo.
+ SysInfo.overrides = {
+ version: "1.2.3",
+ arc: "x64",
+ };
+ MockRegistrar.register("@mozilla.org/system-info;1", SysInfo);
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ let FOG = Cc["@mozilla.org/toolkit/glean;1"].createInstance(Ci.nsIFOG);
+ FOG.initializeFOG();
+});
+
+add_task(function test_osversion_is_set() {
+ Assert.equal(
+ "1.2.3",
+ Glean.fogValidation.osVersion.testGetValue("fog-validation")
+ );
+});
+
+add_task(function test_fog_counter_works() {
+ Glean.testOnly.badCode.add(31);
+ Assert.equal(31, Glean.testOnly.badCode.testGetValue("test-ping"));
+});
+
+add_task(async function test_fog_string_works() {
+ const value = "a cheesy string!";
+ Glean.testOnly.cheesyString.set(value);
+
+ Assert.equal(value, Glean.testOnly.cheesyString.testGetValue("test-ping"));
+});
+
+add_task(async function test_fog_string_list_works() {
+ const value = "a cheesy string!";
+ const value2 = "a cheesier string!";
+ const value3 = "the cheeziest of strings.";
+
+ const cheeseList = [value, value2];
+ Glean.testOnly.cheesyStringList.set(cheeseList);
+
+ let val = Glean.testOnly.cheesyStringList.testGetValue();
+ // Note: This is incredibly fragile and will break if we ever rearrange items
+ // in the string list.
+ Assert.deepEqual(cheeseList, val);
+
+ Glean.testOnly.cheesyStringList.add(value3);
+ Assert.ok(Glean.testOnly.cheesyStringList.testGetValue().includes(value3));
+});
+
+add_task(async function test_fog_timespan_works() {
+ // We start, briefly sleep and then stop.
+ // That guarantees some time to measure.
+ Glean.testOnly.canWeTimeIt.start();
+ await sleep(10);
+ Glean.testOnly.canWeTimeIt.stop();
+
+ Assert.ok(Glean.testOnly.canWeTimeIt.testGetValue("test-ping") > 0);
+});
+
+add_task(async function test_fog_uuid_works() {
+ const kTestUuid = "decafdec-afde-cafd-ecaf-decafdecafde";
+ Glean.testOnly.whatIdIt.set(kTestUuid);
+ Assert.equal(kTestUuid, Glean.testOnly.whatIdIt.testGetValue("test-ping"));
+
+ Glean.testOnly.whatIdIt.generateAndSet();
+ // Since we generate v4 UUIDs, and the first character of the third group
+ // isn't 4, this won't ever collide with kTestUuid.
+ Assert.notEqual(kTestUuid, Glean.testOnly.whatIdIt.testGetValue("test-ping"));
+});
+
+// Enable test after bug 1677448 is fixed.
+add_task({ skip_if: () => true }, function test_fog_datetime_works() {
+ const value = new Date("2020-06-11T12:00:00");
+
+ Glean.testOnly.whatADate.set(value.getTime() * 1000);
+
+ const received = Glean.testOnly.whatADate.testGetValue("test-ping");
+ Assert.ok(received.startsWith("2020-06-11T12:00:00"));
+});
+
+add_task(function test_fog_boolean_works() {
+ Glean.testOnly.canWeFlagIt.set(false);
+ Assert.equal(false, Glean.testOnly.canWeFlagIt.testGetValue("test-ping"));
+ // While you're here, might as well test that the ping name's optional.
+ Assert.equal(false, Glean.testOnly.canWeFlagIt.testGetValue());
+});
+
+add_task(async function test_fog_event_works() {
+ Glean.testOnlyIpc.noExtraEvent.record();
+ // FIXME(bug 1678567): Check that the value was recorded when we can.
+ // Assert.ok(Glean.testOnlyIpc.noExtraEvent.testGetValue("store1"));
+
+ let extra = { extra1: "can set extras", extra2: "passing more data" };
+ Glean.testOnlyIpc.anEvent.record(extra);
+ // FIXME(bug 1678567): Check that the value was recorded when we can.
+ // Assert.ok(Glean.testOnlyIpc.anEvent.testGetValue("store1"));
+});
+
+add_task(async function test_fog_memory_distribution_works() {
+ Glean.testOnly.doYouRemember.accumulate(7);
+ Glean.testOnly.doYouRemember.accumulate(17);
+
+ let data = Glean.testOnly.doYouRemember.testGetValue("test-ping");
+ // `data.sum` is in bytes, but the metric is in MB.
+ Assert.equal(24 * 1024 * 1024, data.sum, "Sum's correct");
+ for (let [bucket, count] of Object.entries(data.values)) {
+ Assert.ok(
+ count == 0 || (count == 1 && (bucket == 17520006 || bucket == 7053950)),
+ "Only two buckets have a sample"
+ );
+ }
+});
+
+add_task(function test_fog_custom_pings() {
+ Assert.ok("onePingOnly" in GleanPings);
+ // Don't bother sending it, we'll test that in the integration suite.
+ // See also bug 1681742.
+});
+
+add_task(async function test_fog_timing_distribution_works() {
+ let t1 = Glean.testOnly.whatTimeIsIt.start();
+ let t2 = Glean.testOnly.whatTimeIsIt.start();
+
+ await sleep(5);
+
+ let t3 = Glean.testOnly.whatTimeIsIt.start();
+ Glean.testOnly.whatTimeIsIt.cancel(t1);
+
+ await sleep(5);
+
+ Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t2); // 10ms
+ Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t3); // 5ms
+
+ let data = Glean.testOnly.whatTimeIsIt.testGetValue();
+ const NANOS_IN_MILLIS = 1e6;
+
+ // Variance in timing makes getting the sum impossible to know.
+ Assert.greater(data.sum, 15 * NANOS_IN_MILLIS, "Total time elapsed: > 15ms");
+
+ // No guarantees from timers means no guarantees on buckets.
+ // But we can guarantee it's only two samples.
+ Assert.equal(
+ 2,
+ Object.entries(data.values).reduce(
+ (acc, [bucket, count]) => acc + count,
+ 0
+ ),
+ "Only two buckets with samples"
+ );
+});
diff --git a/toolkit/components/glean/xpcshell/xpcshell.ini b/toolkit/components/glean/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..8bc1f67948
--- /dev/null
+++ b/toolkit/components/glean/xpcshell/xpcshell.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+firefox-appdir = browser
+
+[test_Glean.js]