glean_core/metrics/
object.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use std::sync::Arc;
6
7use crate::common_metric_data::CommonMetricDataInternal;
8use crate::error_recording::{record_error, test_get_num_recorded_errors, ErrorType};
9use crate::metrics::JsonValue;
10use crate::metrics::Metric;
11use crate::metrics::MetricType;
12use crate::storage::StorageManager;
13use crate::Glean;
14use crate::{CommonMetricData, TestGetValue};
15
16/// An object metric.
17///
18/// Record structured data.
19/// The value must adhere to a predefined structure and is serialized into JSON.
20#[derive(Clone, Debug)]
21pub struct ObjectMetric {
22    meta: Arc<CommonMetricDataInternal>,
23}
24
25impl MetricType for ObjectMetric {
26    fn meta(&self) -> &CommonMetricDataInternal {
27        &self.meta
28    }
29}
30
31// IMPORTANT:
32//
33// When changing this implementation, make sure all the operations are
34// also declared in the related trait in `../traits/`.
35impl ObjectMetric {
36    /// Creates a new object metric.
37    pub fn new(meta: CommonMetricData) -> Self {
38        Self {
39            meta: Arc::new(meta.into()),
40        }
41    }
42
43    /// Sets to the specified structure.
44    ///
45    /// # Arguments
46    ///
47    /// * `glean` - the Glean instance this metric belongs to.
48    /// * `value` - the value to set.
49    #[doc(hidden)]
50    pub fn set_sync(&self, glean: &Glean, value: JsonValue) {
51        if !self.should_record(glean) {
52            return;
53        }
54
55        let value = Metric::Object(serde_json::to_string(&value).unwrap());
56        glean.storage().record(glean, &self.meta, &value)
57    }
58
59    /// Sets to the specified structure.
60    ///
61    /// No additional verification is done.
62    /// The shape needs to be externally verified.
63    ///
64    /// # Arguments
65    ///
66    /// * `value` - the value to set.
67    pub fn set(&self, value: JsonValue) {
68        let metric = self.clone();
69        crate::launch_with_glean(move |glean| metric.set_sync(glean, value))
70    }
71
72    /// Sets to the specified structure.
73    ///
74    /// Parses the passed JSON string.
75    /// If it can't be parsed into a valid object it records an invalid value error.
76    ///
77    /// Note: This does not check the structure. This needs to be done by the wrapper.
78    ///
79    /// # Arguments
80    ///
81    /// * `object` - JSON representation of the object to set.
82    pub fn set_string(&self, object: String) {
83        let metric = self.clone();
84        crate::launch_with_glean(move |glean| {
85            let object = match serde_json::from_str(&object) {
86                Ok(object) => object,
87                Err(_) => {
88                    let msg = "Value did not match predefined schema";
89                    record_error(glean, &metric.meta, ErrorType::InvalidValue, msg, None);
90                    return;
91                }
92            };
93            metric.set_sync(glean, object)
94        })
95    }
96
97    /// Record an `InvalidValue` error for this metric.
98    ///
99    /// Only to be used by the RLB.
100    // TODO(bug 1691073): This can probably go once we have a more generic mechanism to record
101    // errors
102    pub fn record_schema_error(&self) {
103        let metric = self.clone();
104        crate::launch_with_glean(move |glean| {
105            let msg = "Value did not match predefined schema";
106            record_error(glean, &metric.meta, ErrorType::InvalidValue, msg, None);
107        });
108    }
109
110    /// Get current value
111    #[doc(hidden)]
112    pub fn get_value<'a, S: Into<Option<&'a str>>>(
113        &self,
114        glean: &Glean,
115        ping_name: S,
116    ) -> Option<String> {
117        let queried_ping_name = ping_name
118            .into()
119            .unwrap_or_else(|| &self.meta().inner.send_in_pings[0]);
120
121        match StorageManager.snapshot_metric_for_test(
122            glean.storage(),
123            queried_ping_name,
124            &self.meta.identifier(glean),
125            self.meta.inner.lifetime,
126        ) {
127            Some(Metric::Object(o)) => Some(o),
128            _ => None,
129        }
130    }
131
132    /// **Exported for test purposes.**
133    ///
134    /// Gets the number of recorded errors for the given metric and error type.
135    ///
136    /// # Arguments
137    ///
138    /// * `error` - The type of error
139    /// * `ping_name` - represents the optional name of the ping to retrieve the
140    ///   metric for. inner to the first value in `send_in_pings`.
141    ///
142    /// # Returns
143    ///
144    /// The number of errors reported.
145    pub fn test_get_num_recorded_errors(&self, error: ErrorType) -> i32 {
146        crate::block_on_dispatcher();
147
148        crate::core::with_glean(|glean| {
149            test_get_num_recorded_errors(glean, self.meta(), error).unwrap_or(0)
150        })
151    }
152}
153
154impl TestGetValue<JsonValue> for ObjectMetric {
155    /// **Test-only API (exported for FFI purposes).**
156    ///
157    /// Gets the currently stored value as JSON.
158    ///
159    /// This doesn't clear the stored value.
160    fn test_get_value(&self, ping_name: Option<String>) -> Option<JsonValue> {
161        crate::block_on_dispatcher();
162        let value = crate::core::with_glean(|glean| self.get_value(glean, ping_name.as_deref()));
163        // We only store valid JSON
164        value.map(|val| serde_json::from_str(&val).unwrap())
165    }
166}