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