summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/api/src/private
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/glean/api/src/private')
-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
13 files changed, 2471 insertions, 0 deletions
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"
+ );
+ }
+}