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 
5 use std::time::Duration;
6 
7 mod common;
8 use crate::common::*;
9 
10 use serde_json::json;
11 
12 use glean_core::metrics::*;
13 use glean_core::storage::StorageManager;
14 use glean_core::{test_get_num_recorded_errors, ErrorType};
15 use glean_core::{CommonMetricData, Lifetime};
16 
17 // Tests ported from glean-ac
18 
19 #[test]
serializer_should_correctly_serialize_timespans()20 fn serializer_should_correctly_serialize_timespans() {
21     let (mut tempdir, _) = tempdir();
22 
23     let duration = 60;
24 
25     {
26         // We give tempdir to the `new_glean` function...
27         let (glean, dir) = new_glean(Some(tempdir));
28         // And then we get it back once that function returns.
29         tempdir = dir;
30 
31         let mut metric = TimespanMetric::new(
32             CommonMetricData {
33                 name: "timespan_metric".into(),
34                 category: "telemetry".into(),
35                 send_in_pings: vec!["store1".into()],
36                 disabled: false,
37                 lifetime: Lifetime::Ping,
38                 ..Default::default()
39             },
40             TimeUnit::Nanosecond,
41         );
42 
43         metric.set_start(&glean, 0);
44         metric.set_stop(&glean, duration);
45 
46         let val = metric
47             .test_get_value(&glean, "store1")
48             .expect("Value should be stored");
49         assert_eq!(duration, val, "Recorded timespan should be positive.");
50     }
51 
52     // Make a new Glean instance here, which should force reloading of the data from disk
53     // so we can ensure it persisted, because it has User lifetime
54     {
55         let (glean, _) = new_glean(Some(tempdir));
56         let snapshot = StorageManager
57             .snapshot_as_json(glean.storage(), "store1", true)
58             .unwrap();
59 
60         assert_eq!(
61             json!({"timespan": {"telemetry.timespan_metric": { "value": duration, "time_unit": "nanosecond" }}}),
62             snapshot
63         );
64     }
65 }
66 
67 #[test]
single_elapsed_time_must_be_recorded()68 fn single_elapsed_time_must_be_recorded() {
69     let (glean, _t) = new_glean(None);
70 
71     let mut metric = TimespanMetric::new(
72         CommonMetricData {
73             name: "timespan_metric".into(),
74             category: "telemetry".into(),
75             send_in_pings: vec!["store1".into()],
76             disabled: false,
77             lifetime: Lifetime::Ping,
78             ..Default::default()
79         },
80         TimeUnit::Nanosecond,
81     );
82 
83     let duration = 60;
84 
85     metric.set_start(&glean, 0);
86     metric.set_stop(&glean, duration);
87 
88     let val = metric
89         .test_get_value(&glean, "store1")
90         .expect("Value should be stored");
91     assert_eq!(duration, val, "Recorded timespan should be positive.");
92 }
93 
94 // SKIPPED from glean-ac: multiple elapsed times must be correctly accumulated.
95 // replaced by below after API change.
96 
97 #[test]
second_timer_run_is_skipped()98 fn second_timer_run_is_skipped() {
99     let (glean, _t) = new_glean(None);
100 
101     let mut metric = TimespanMetric::new(
102         CommonMetricData {
103             name: "timespan_metric".into(),
104             category: "telemetry".into(),
105             send_in_pings: vec!["store1".into()],
106             disabled: false,
107             lifetime: Lifetime::Ping,
108             ..Default::default()
109         },
110         TimeUnit::Nanosecond,
111     );
112 
113     let duration = 60;
114     metric.set_start(&glean, 0);
115     metric.set_stop(&glean, duration);
116 
117     // No error should be recorded here: we had no prior value stored.
118     assert!(
119         test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidState, None).is_err()
120     );
121 
122     let first_value = metric.test_get_value(&glean, "store1").unwrap();
123     assert_eq!(duration, first_value);
124 
125     metric.set_start(&glean, 0);
126     metric.set_stop(&glean, duration * 2);
127 
128     let second_value = metric.test_get_value(&glean, "store1").unwrap();
129     assert_eq!(second_value, first_value);
130 
131     // Make sure that the error has been recorded: we had a stored value, the
132     // new measurement was dropped.
133     assert_eq!(
134         Ok(1),
135         test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidState, None)
136     );
137 }
138 
139 #[test]
recorded_time_conforms_to_resolution()140 fn recorded_time_conforms_to_resolution() {
141     let (glean, _t) = new_glean(None);
142 
143     let mut ns_metric = TimespanMetric::new(
144         CommonMetricData {
145             name: "timespan_ns".into(),
146             category: "telemetry".into(),
147             send_in_pings: vec!["store1".into()],
148             disabled: false,
149             lifetime: Lifetime::Ping,
150             ..Default::default()
151         },
152         TimeUnit::Nanosecond,
153     );
154 
155     let mut minute_metric = TimespanMetric::new(
156         CommonMetricData {
157             name: "timespan_m".into(),
158             category: "telemetry".into(),
159             send_in_pings: vec!["store1".into()],
160             disabled: false,
161             lifetime: Lifetime::Ping,
162             ..Default::default()
163         },
164         TimeUnit::Minute,
165     );
166 
167     let duration = 60;
168     ns_metric.set_start(&glean, 0);
169     ns_metric.set_stop(&glean, duration);
170 
171     let ns_value = ns_metric.test_get_value(&glean, "store1").unwrap();
172     assert_eq!(duration, ns_value);
173 
174     // 1 minute in nanoseconds
175     let duration_minute = 60 * 1_000_000_000;
176     minute_metric.set_start(&glean, 0);
177     minute_metric.set_stop(&glean, duration_minute);
178 
179     let minute_value = minute_metric.test_get_value(&glean, "store1").unwrap();
180     assert_eq!(1, minute_value);
181 }
182 
183 // SKIPPED from glean-ac: accumulated short-lived timespans should not be discarded
184 
185 #[test]
cancel_does_not_store()186 fn cancel_does_not_store() {
187     let (glean, _t) = new_glean(None);
188 
189     let mut metric = TimespanMetric::new(
190         CommonMetricData {
191             name: "timespan_metric".into(),
192             category: "telemetry".into(),
193             send_in_pings: vec!["store1".into()],
194             disabled: false,
195             lifetime: Lifetime::Ping,
196             ..Default::default()
197         },
198         TimeUnit::Nanosecond,
199     );
200 
201     metric.set_start(&glean, 0);
202     metric.cancel();
203 
204     assert_eq!(None, metric.test_get_value(&glean, "store1"));
205 }
206 
207 #[test]
nothing_stored_before_stop()208 fn nothing_stored_before_stop() {
209     let (glean, _t) = new_glean(None);
210 
211     let mut metric = TimespanMetric::new(
212         CommonMetricData {
213             name: "timespan_metric".into(),
214             category: "telemetry".into(),
215             send_in_pings: vec!["store1".into()],
216             disabled: false,
217             lifetime: Lifetime::Ping,
218             ..Default::default()
219         },
220         TimeUnit::Nanosecond,
221     );
222 
223     let duration = 60;
224 
225     metric.set_start(&glean, 0);
226 
227     assert_eq!(None, metric.test_get_value(&glean, "store1"));
228 
229     metric.set_stop(&glean, duration);
230     assert_eq!(duration, metric.test_get_value(&glean, "store1").unwrap());
231 }
232 
233 #[test]
set_raw_time()234 fn set_raw_time() {
235     let (glean, _t) = new_glean(None);
236 
237     let metric = TimespanMetric::new(
238         CommonMetricData {
239             name: "timespan_metric".into(),
240             category: "telemetry".into(),
241             send_in_pings: vec!["store1".into()],
242             disabled: false,
243             lifetime: Lifetime::Ping,
244             ..Default::default()
245         },
246         TimeUnit::Nanosecond,
247     );
248 
249     let time = Duration::from_secs(1);
250     metric.set_raw(&glean, time);
251 
252     let time_in_ns = time.as_nanos() as u64;
253     assert_eq!(Some(time_in_ns), metric.test_get_value(&glean, "store1"));
254 }
255 
256 #[test]
set_raw_time_does_nothing_when_timer_running()257 fn set_raw_time_does_nothing_when_timer_running() {
258     let (glean, _t) = new_glean(None);
259 
260     let mut metric = TimespanMetric::new(
261         CommonMetricData {
262             name: "timespan_metric".into(),
263             category: "telemetry".into(),
264             send_in_pings: vec!["store1".into()],
265             disabled: false,
266             lifetime: Lifetime::Ping,
267             ..Default::default()
268         },
269         TimeUnit::Nanosecond,
270     );
271 
272     let time = Duration::from_secs(42);
273 
274     metric.set_start(&glean, 0);
275     metric.set_raw(&glean, time);
276     metric.set_stop(&glean, 60);
277 
278     // We expect the start/stop value, not the raw value.
279     assert_eq!(Some(60), metric.test_get_value(&glean, "store1"));
280 
281     // Make sure that the error has been recorded
282     assert_eq!(
283         Ok(1),
284         test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidState, None)
285     );
286 }
287 
288 #[test]
timespan_is_not_tracked_across_upload_toggle()289 fn timespan_is_not_tracked_across_upload_toggle() {
290     let (mut glean, _t) = new_glean(None);
291 
292     let mut metric = TimespanMetric::new(
293         CommonMetricData {
294             name: "timespan_metric".into(),
295             category: "telemetry".into(),
296             send_in_pings: vec!["store1".into()],
297             disabled: false,
298             lifetime: Lifetime::Ping,
299             ..Default::default()
300         },
301         TimeUnit::Nanosecond,
302     );
303 
304     // Timer is started.
305     metric.set_start(&glean, 0);
306     // User disables telemetry upload.
307     glean.set_upload_enabled(false);
308     // App code eventually stops the timer.
309     // We should clear internal state as upload is disabled.
310     metric.set_stop(&glean, 40);
311 
312     // App code eventually starts the timer again.
313     // Upload is disabled, so this should not have any effect.
314     metric.set_start(&glean, 100);
315     // User enables telemetry upload again.
316     glean.set_upload_enabled(true);
317     // App code eventually stops the timer.
318     // None should be running.
319     metric.set_stop(&glean, 200);
320 
321     // Nothing should have been recorded.
322     assert_eq!(None, metric.test_get_value(&glean, "store1"));
323 
324     // Make sure that the error has been recorded
325     assert_eq!(
326         Ok(1),
327         test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidState, None)
328     );
329 }
330 
331 #[test]
time_cannot_go_backwards()332 fn time_cannot_go_backwards() {
333     let (glean, _t) = new_glean(None);
334 
335     let mut metric: TimespanMetric = TimespanMetric::new(
336         CommonMetricData {
337             name: "raw_timespan".into(),
338             category: "test".into(),
339             send_in_pings: vec!["test1".into()],
340             ..Default::default()
341         },
342         TimeUnit::Millisecond,
343     );
344 
345     // Time cannot go backwards.
346     metric.set_start(&glean, 10);
347     metric.set_stop(&glean, 0);
348     assert!(metric.test_get_value(&glean, "test1").is_none());
349     assert_eq!(
350         Ok(1),
351         test_get_num_recorded_errors(&glean, metric.meta(), ErrorType::InvalidValue, None),
352     );
353 }
354