glean_core/
core_metrics.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 crate::metrics::{
6    Datetime, DatetimeMetric, QuantityMetric, StringMetric, TimeUnit, TimespanMetric,
7};
8use crate::{CommonMetricData, Lifetime};
9
10use once_cell::sync::Lazy;
11
12/// Metrics included in every ping as `client_info`.
13#[derive(Debug, Default)]
14pub struct ClientInfoMetrics {
15    /// The build identifier generated by the CI system (e.g. "1234/A").
16    pub app_build: String,
17    /// The user visible version string (e.g. "1.0.3").
18    pub app_display_version: String,
19    /// The app's build date
20    pub app_build_date: Datetime,
21
22    /// The architecture of the device (e.g. "arm", "x86").
23    pub architecture: String,
24    /// The name of the operating system (e.g. "Linux", "Android", "iOS").
25    pub os_version: String,
26
27    /// The product-provided release channel (e.g. "beta").
28    pub channel: Option<String>,
29    /// The Android specific SDK version of the software running on this hardware device (e.g. "23").
30    pub android_sdk_version: Option<String>,
31    /// The Windows specific OS build version (e.g. 19043)
32    pub windows_build_number: Option<i64>,
33    /// The manufacturer of the device the application is running on.
34    /// Not set if the device manufacturer can't be determined (e.g. on Desktop).
35    pub device_manufacturer: Option<String>,
36    /// The model of the device the application is running on.
37    /// On Android, this is Build.MODEL, the user-visible marketing name, like "Pixel 2 XL".
38    /// Not set if the device model can't be determined (e.g. on Desktop).
39    pub device_model: Option<String>,
40    /// The locale of the application during initialization (e.g. "es-ES").
41    /// If the locale can't be determined on the system, the value is "und", to indicate "undetermined".
42    pub locale: Option<String>,
43}
44
45/// Optional product attribution metrics carried in `client_info.attribution`.
46#[derive(Clone, Debug, Default, PartialEq)]
47pub struct AttributionMetrics {
48    /// The attribution source (e.g. "google-play").
49    pub source: Option<String>,
50    /// The attribution medium (e.g. "organic" for a search engine).
51    pub medium: Option<String>,
52    /// The attribution campaign (e.g. "mozilla-org").
53    pub campaign: Option<String>,
54    /// The attribution term (e.g. "browser with developer tools for android").
55    pub term: Option<String>,
56    /// The attribution content (e.g. "firefoxview").
57    pub content: Option<String>,
58}
59
60impl AttributionMetrics {
61    /// Update self with any non-`None` fields from `other`.
62    pub fn update(&mut self, other: AttributionMetrics) {
63        if let Some(source) = other.source {
64            self.source = Some(source);
65        }
66        if let Some(medium) = other.medium {
67            self.medium = Some(medium);
68        }
69        if let Some(campaign) = other.campaign {
70            self.campaign = Some(campaign);
71        }
72        if let Some(term) = other.term {
73            self.term = Some(term);
74        }
75        if let Some(content) = other.content {
76            self.content = Some(content);
77        }
78    }
79}
80
81/// Optional product distribution metrics carried in `client_info.distribution`.
82#[derive(Clone, Debug, Default, PartialEq)]
83pub struct DistributionMetrics {
84    /// The distribution name (e.g. "MozillaOnline").
85    pub name: Option<String>,
86}
87
88impl DistributionMetrics {
89    /// Update self with any non-`None` fields from `other`.
90    pub fn update(&mut self, other: DistributionMetrics) {
91        if let Some(name) = other.name {
92            self.name = Some(name);
93        }
94    }
95}
96
97/// Metrics included in every ping as `client_info`.
98impl ClientInfoMetrics {
99    /// Creates the client info with dummy values for all.
100    pub fn unknown() -> Self {
101        ClientInfoMetrics {
102            app_build: "Unknown".to_string(),
103            app_display_version: "Unknown".to_string(),
104            app_build_date: Datetime::default(),
105            architecture: "Unknown".to_string(),
106            os_version: "Unknown".to_string(),
107            channel: Some("Unknown".to_string()),
108            android_sdk_version: None,
109            windows_build_number: None,
110            device_manufacturer: None,
111            device_model: None,
112            locale: None,
113        }
114    }
115}
116
117#[allow(non_upper_case_globals)]
118pub mod internal_metrics {
119    use super::*;
120
121    pub static app_build: Lazy<StringMetric> = Lazy::new(|| {
122        StringMetric::new(CommonMetricData {
123            name: "app_build".into(),
124            category: "".into(),
125            send_in_pings: vec!["glean_client_info".into()],
126            lifetime: Lifetime::Application,
127            disabled: false,
128            ..Default::default()
129        })
130    });
131
132    pub static app_display_version: Lazy<StringMetric> = Lazy::new(|| {
133        StringMetric::new(CommonMetricData {
134            name: "app_display_version".into(),
135            category: "".into(),
136            send_in_pings: vec!["glean_client_info".into()],
137            lifetime: Lifetime::Application,
138            disabled: false,
139            ..Default::default()
140        })
141    });
142
143    pub static app_build_date: Lazy<DatetimeMetric> = Lazy::new(|| {
144        DatetimeMetric::new(
145            CommonMetricData {
146                name: "build_date".into(),
147                category: "".into(),
148                send_in_pings: vec!["glean_client_info".into()],
149                lifetime: Lifetime::Application,
150                disabled: false,
151                ..Default::default()
152            },
153            TimeUnit::Second,
154        )
155    });
156
157    pub static app_channel: Lazy<StringMetric> = Lazy::new(|| {
158        StringMetric::new(CommonMetricData {
159            name: "app_channel".into(),
160            category: "".into(),
161            send_in_pings: vec!["glean_client_info".into()],
162            lifetime: Lifetime::Application,
163            disabled: false,
164            ..Default::default()
165        })
166    });
167
168    pub static os_version: Lazy<StringMetric> = Lazy::new(|| {
169        StringMetric::new(CommonMetricData {
170            name: "os_version".into(),
171            category: "".into(),
172            send_in_pings: vec!["glean_client_info".into()],
173            lifetime: Lifetime::Application,
174            disabled: false,
175            ..Default::default()
176        })
177    });
178
179    pub static architecture: Lazy<StringMetric> = Lazy::new(|| {
180        StringMetric::new(CommonMetricData {
181            name: "architecture".into(),
182            category: "".into(),
183            send_in_pings: vec!["glean_client_info".into()],
184            lifetime: Lifetime::Application,
185            disabled: false,
186            ..Default::default()
187        })
188    });
189
190    pub static android_sdk_version: Lazy<StringMetric> = Lazy::new(|| {
191        StringMetric::new(CommonMetricData {
192            name: "android_sdk_version".into(),
193            category: "".into(),
194            send_in_pings: vec!["glean_client_info".into()],
195            lifetime: Lifetime::Application,
196            disabled: false,
197            ..Default::default()
198        })
199    });
200
201    pub static windows_build_number: Lazy<QuantityMetric> = Lazy::new(|| {
202        QuantityMetric::new(CommonMetricData {
203            name: "windows_build_number".into(),
204            category: "".into(),
205            send_in_pings: vec!["glean_client_info".into()],
206            lifetime: Lifetime::Application,
207            disabled: false,
208            ..Default::default()
209        })
210    });
211
212    pub static device_manufacturer: Lazy<StringMetric> = Lazy::new(|| {
213        StringMetric::new(CommonMetricData {
214            name: "device_manufacturer".into(),
215            category: "".into(),
216            send_in_pings: vec!["glean_client_info".into()],
217            lifetime: Lifetime::Application,
218            disabled: false,
219            ..Default::default()
220        })
221    });
222
223    pub static device_model: Lazy<StringMetric> = Lazy::new(|| {
224        StringMetric::new(CommonMetricData {
225            name: "device_model".into(),
226            category: "".into(),
227            send_in_pings: vec!["glean_client_info".into()],
228            lifetime: Lifetime::Application,
229            disabled: false,
230            ..Default::default()
231        })
232    });
233
234    pub static locale: Lazy<StringMetric> = Lazy::new(|| {
235        StringMetric::new(CommonMetricData {
236            name: "locale".into(),
237            category: "".into(),
238            send_in_pings: vec!["glean_client_info".into()],
239            lifetime: Lifetime::Application,
240            disabled: false,
241            ..Default::default()
242        })
243    });
244
245    pub static baseline_duration: Lazy<TimespanMetric> = Lazy::new(|| {
246        TimespanMetric::new(
247            CommonMetricData {
248                name: "duration".into(),
249                category: "glean.baseline".into(),
250                send_in_pings: vec!["baseline".into()],
251                lifetime: Lifetime::Ping,
252                disabled: false,
253                ..Default::default()
254            },
255            TimeUnit::Second,
256        )
257    });
258}
259
260#[cfg(test)]
261mod test {
262    use super::*;
263
264    #[test]
265    fn update_attribution() {
266        let mut attr: AttributionMetrics = Default::default();
267        let empty: AttributionMetrics = Default::default();
268
269        // Ensure the identity operation works.
270        attr.update(empty.clone());
271        assert_eq!(None, attr.source);
272
273        // Ensure simple updates work.
274        attr.update(AttributionMetrics {
275            source: Some("a source".into()),
276            ..Default::default()
277        });
278        assert_eq!(Some("a source".into()), attr.source);
279
280        // Ensure None doesn't overwrite.
281        attr.update(empty);
282        assert_eq!(Some("a source".into()), attr.source);
283
284        // Ensure updates of Some work.
285        attr.update(AttributionMetrics {
286            source: Some("another source".into()),
287            ..Default::default()
288        });
289        assert_eq!(Some("another source".into()), attr.source);
290    }
291
292    #[test]
293    fn update_distribution() {
294        let mut dist: DistributionMetrics = Default::default();
295        let empty: DistributionMetrics = Default::default();
296
297        // Ensure the identity operation works.
298        dist.update(empty.clone());
299        assert_eq!(None, dist.name);
300
301        // Ensure simple updates work.
302        dist.update(DistributionMetrics {
303            name: Some("a name".into()),
304        });
305        assert_eq!(Some("a name".into()), dist.name);
306
307        // Ensure None doesn't overwrite.
308        dist.update(empty);
309        assert_eq!(Some("a name".into()), dist.name);
310
311        // Ensure updates of Some work.
312        dist.update(DistributionMetrics {
313            name: Some("another name".into()),
314        });
315        assert_eq!(Some("another name".into()), dist.name);
316    }
317}