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 mod common;
6 use crate::common::*;
7 
8 use serde_json::json;
9 
10 use glean_core::metrics::*;
11 use glean_core::storage::StorageManager;
12 use glean_core::{test_get_num_recorded_errors, ErrorType};
13 use glean_core::{CommonMetricData, Lifetime};
14 
15 // Tests ported from glean-ac
16 
17 mod linear {
18     use super::*;
19 
20     #[test]
serializer_should_correctly_serialize_custom_distribution()21     fn serializer_should_correctly_serialize_custom_distribution() {
22         let (mut tempdir, _) = tempdir();
23 
24         {
25             let (glean, dir) = new_glean(Some(tempdir));
26             tempdir = dir;
27 
28             let metric = CustomDistributionMetric::new(
29                 CommonMetricData {
30                     name: "distribution".into(),
31                     category: "telemetry".into(),
32                     send_in_pings: vec!["store1".into()],
33                     disabled: false,
34                     lifetime: Lifetime::Ping,
35                     ..Default::default()
36                 },
37                 1,
38                 100,
39                 100,
40                 HistogramType::Linear,
41             );
42 
43             metric.accumulate_samples_signed(&glean, vec![50]);
44 
45             let snapshot = metric
46                 .test_get_value(&glean, "store1")
47                 .expect("Value should be stored");
48 
49             assert_eq!(snapshot.sum, 50);
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!(50),
62                 snapshot["custom_distribution"]["telemetry.distribution"]["sum"]
63             );
64         }
65     }
66 
67     #[test]
set_value_properly_sets_the_value_in_all_stores()68     fn set_value_properly_sets_the_value_in_all_stores() {
69         let (glean, _t) = new_glean(None);
70         let store_names: Vec<String> = vec!["store1".into(), "store2".into()];
71 
72         let metric = CustomDistributionMetric::new(
73             CommonMetricData {
74                 name: "distribution".into(),
75                 category: "telemetry".into(),
76                 send_in_pings: store_names.clone(),
77                 disabled: false,
78                 lifetime: Lifetime::Ping,
79                 ..Default::default()
80             },
81             1,
82             100,
83             100,
84             HistogramType::Linear,
85         );
86 
87         metric.accumulate_samples_signed(&glean, vec![50]);
88 
89         for store_name in store_names {
90             let snapshot = StorageManager
91                 .snapshot_as_json(glean.storage(), &store_name, true)
92                 .unwrap();
93 
94             assert_eq!(
95                 json!(50),
96                 snapshot["custom_distribution"]["telemetry.distribution"]["sum"]
97             );
98             assert_eq!(
99                 json!(1),
100                 snapshot["custom_distribution"]["telemetry.distribution"]["values"]["50"]
101             );
102         }
103     }
104 
105     // SKIPPED from glean-ac: memory distributions must not accumulate negative values
106     // This test doesn't apply to Rust, because we're using unsigned integers.
107 
108     #[test]
the_accumulate_samples_api_correctly_stores_memory_values()109     fn the_accumulate_samples_api_correctly_stores_memory_values() {
110         let (glean, _t) = new_glean(None);
111 
112         let metric = CustomDistributionMetric::new(
113             CommonMetricData {
114                 name: "distribution".into(),
115                 category: "telemetry".into(),
116                 send_in_pings: vec!["store1".into()],
117                 disabled: false,
118                 lifetime: Lifetime::Ping,
119                 ..Default::default()
120             },
121             1,
122             100,
123             100,
124             HistogramType::Linear,
125         );
126 
127         // Accumulate the samples. We intentionally do not report
128         // negative values to not trigger error reporting.
129         metric.accumulate_samples_signed(&glean, [1, 2, 3].to_vec());
130 
131         let snapshot = metric
132             .test_get_value(&glean, "store1")
133             .expect("Value should be stored");
134 
135         // Check that we got the right sum of samples.
136         assert_eq!(snapshot.sum, 6);
137 
138         // We should get a sample in 3 buckets.
139         // These numbers are a bit magic, but they correspond to
140         // `hist.sample_to_bucket_minimum(i * kb)` for `i = 1..=3`.
141         assert_eq!(1, snapshot.values[&1]);
142         assert_eq!(1, snapshot.values[&2]);
143         assert_eq!(1, snapshot.values[&3]);
144 
145         // No errors should be reported.
146         assert!(test_get_num_recorded_errors(
147             &glean,
148             metric.meta(),
149             ErrorType::InvalidValue,
150             Some("store1")
151         )
152         .is_err());
153     }
154 
155     #[test]
the_accumulate_samples_api_correctly_handles_negative_values()156     fn the_accumulate_samples_api_correctly_handles_negative_values() {
157         let (glean, _t) = new_glean(None);
158 
159         let metric = CustomDistributionMetric::new(
160             CommonMetricData {
161                 name: "distribution".into(),
162                 category: "telemetry".into(),
163                 send_in_pings: vec!["store1".into()],
164                 disabled: false,
165                 lifetime: Lifetime::Ping,
166                 ..Default::default()
167             },
168             1,
169             100,
170             100,
171             HistogramType::Linear,
172         );
173 
174         // Accumulate the samples.
175         metric.accumulate_samples_signed(&glean, [-1, 1, 2, 3].to_vec());
176 
177         let snapshot = metric
178             .test_get_value(&glean, "store1")
179             .expect("Value should be stored");
180 
181         // Check that we got the right sum of samples.
182         assert_eq!(snapshot.sum, 6);
183 
184         // We should get a sample in 3 buckets.
185         // These numbers are a bit magic, but they correspond to
186         // `hist.sample_to_bucket_minimum(i * kb)` for `i = 1..=3`.
187         assert_eq!(1, snapshot.values[&1]);
188         assert_eq!(1, snapshot.values[&2]);
189         assert_eq!(1, snapshot.values[&3]);
190 
191         // 1 error should be reported.
192         assert_eq!(
193             Ok(1),
194             test_get_num_recorded_errors(
195                 &glean,
196                 metric.meta(),
197                 ErrorType::InvalidValue,
198                 Some("store1")
199             )
200         );
201     }
202 
203     #[test]
json_snapshotting_works()204     fn json_snapshotting_works() {
205         let (glean, _t) = new_glean(None);
206         let metric = CustomDistributionMetric::new(
207             CommonMetricData {
208                 name: "distribution".into(),
209                 category: "telemetry".into(),
210                 send_in_pings: vec!["store1".into()],
211                 disabled: false,
212                 lifetime: Lifetime::Ping,
213                 ..Default::default()
214             },
215             1,
216             100,
217             100,
218             HistogramType::Linear,
219         );
220 
221         metric.accumulate_samples_signed(&glean, vec![50]);
222 
223         let snapshot = metric.test_get_value_as_json_string(&glean, "store1");
224         assert!(snapshot.is_some());
225     }
226 }
227 
228 mod exponential {
229     use super::*;
230 
231     #[test]
serializer_should_correctly_serialize_custom_distribution()232     fn serializer_should_correctly_serialize_custom_distribution() {
233         let (mut tempdir, _) = tempdir();
234 
235         {
236             let (glean, dir) = new_glean(Some(tempdir));
237             tempdir = dir;
238 
239             let metric = CustomDistributionMetric::new(
240                 CommonMetricData {
241                     name: "distribution".into(),
242                     category: "telemetry".into(),
243                     send_in_pings: vec!["store1".into()],
244                     disabled: false,
245                     lifetime: Lifetime::Ping,
246                     ..Default::default()
247                 },
248                 1,
249                 100,
250                 10,
251                 HistogramType::Exponential,
252             );
253 
254             metric.accumulate_samples_signed(&glean, vec![50]);
255 
256             let snapshot = metric
257                 .test_get_value(&glean, "store1")
258                 .expect("Value should be stored");
259 
260             assert_eq!(snapshot.sum, 50);
261         }
262 
263         // Make a new Glean instance here, which should force reloading of the data from disk
264         // so we can ensure it persisted, because it has User lifetime
265         {
266             let (glean, _) = new_glean(Some(tempdir));
267             let snapshot = StorageManager
268                 .snapshot_as_json(glean.storage(), "store1", true)
269                 .unwrap();
270 
271             assert_eq!(
272                 json!(50),
273                 snapshot["custom_distribution"]["telemetry.distribution"]["sum"]
274             );
275         }
276     }
277 
278     #[test]
set_value_properly_sets_the_value_in_all_stores()279     fn set_value_properly_sets_the_value_in_all_stores() {
280         let (glean, _t) = new_glean(None);
281         let store_names: Vec<String> = vec!["store1".into(), "store2".into()];
282 
283         let metric = CustomDistributionMetric::new(
284             CommonMetricData {
285                 name: "distribution".into(),
286                 category: "telemetry".into(),
287                 send_in_pings: store_names.clone(),
288                 disabled: false,
289                 lifetime: Lifetime::Ping,
290                 ..Default::default()
291             },
292             1,
293             100,
294             10,
295             HistogramType::Exponential,
296         );
297 
298         metric.accumulate_samples_signed(&glean, vec![50]);
299 
300         for store_name in store_names {
301             let snapshot = StorageManager
302                 .snapshot_as_json(glean.storage(), &store_name, true)
303                 .unwrap();
304 
305             assert_eq!(
306                 json!(50),
307                 snapshot["custom_distribution"]["telemetry.distribution"]["sum"]
308             );
309             assert_eq!(
310                 json!(1),
311                 snapshot["custom_distribution"]["telemetry.distribution"]["values"]["29"]
312             );
313         }
314     }
315 
316     // SKIPPED from glean-ac: memory distributions must not accumulate negative values
317     // This test doesn't apply to Rust, because we're using unsigned integers.
318 
319     #[test]
the_accumulate_samples_api_correctly_stores_memory_values()320     fn the_accumulate_samples_api_correctly_stores_memory_values() {
321         let (glean, _t) = new_glean(None);
322 
323         let metric = CustomDistributionMetric::new(
324             CommonMetricData {
325                 name: "distribution".into(),
326                 category: "telemetry".into(),
327                 send_in_pings: vec!["store1".into()],
328                 disabled: false,
329                 lifetime: Lifetime::Ping,
330                 ..Default::default()
331             },
332             1,
333             100,
334             10,
335             HistogramType::Exponential,
336         );
337 
338         // Accumulate the samples. We intentionally do not report
339         // negative values to not trigger error reporting.
340         metric.accumulate_samples_signed(&glean, [1, 2, 3].to_vec());
341 
342         let snapshot = metric
343             .test_get_value(&glean, "store1")
344             .expect("Value should be stored");
345 
346         // Check that we got the right sum of samples.
347         assert_eq!(snapshot.sum, 6);
348 
349         // We should get a sample in 3 buckets.
350         // These numbers are a bit magic, but they correspond to
351         // `hist.sample_to_bucket_minimum(i * kb)` for `i = 1..=3`.
352         assert_eq!(1, snapshot.values[&1]);
353         assert_eq!(1, snapshot.values[&2]);
354         assert_eq!(1, snapshot.values[&3]);
355 
356         // No errors should be reported.
357         assert!(test_get_num_recorded_errors(
358             &glean,
359             metric.meta(),
360             ErrorType::InvalidValue,
361             Some("store1")
362         )
363         .is_err());
364     }
365 
366     #[test]
the_accumulate_samples_api_correctly_handles_negative_values()367     fn the_accumulate_samples_api_correctly_handles_negative_values() {
368         let (glean, _t) = new_glean(None);
369 
370         let metric = CustomDistributionMetric::new(
371             CommonMetricData {
372                 name: "distribution".into(),
373                 category: "telemetry".into(),
374                 send_in_pings: vec!["store1".into()],
375                 disabled: false,
376                 lifetime: Lifetime::Ping,
377                 ..Default::default()
378             },
379             1,
380             100,
381             10,
382             HistogramType::Exponential,
383         );
384 
385         // Accumulate the samples.
386         metric.accumulate_samples_signed(&glean, [-1, 1, 2, 3].to_vec());
387 
388         let snapshot = metric
389             .test_get_value(&glean, "store1")
390             .expect("Value should be stored");
391 
392         // Check that we got the right sum of samples.
393         assert_eq!(snapshot.sum, 6);
394 
395         // We should get a sample in 3 buckets.
396         // These numbers are a bit magic, but they correspond to
397         // `hist.sample_to_bucket_minimum(i * kb)` for `i = 1..=3`.
398         assert_eq!(1, snapshot.values[&1]);
399         assert_eq!(1, snapshot.values[&2]);
400         assert_eq!(1, snapshot.values[&3]);
401 
402         // 1 error should be reported.
403         assert_eq!(
404             Ok(1),
405             test_get_num_recorded_errors(
406                 &glean,
407                 metric.meta(),
408                 ErrorType::InvalidValue,
409                 Some("store1")
410             )
411         );
412     }
413 
414     #[test]
json_snapshotting_works()415     fn json_snapshotting_works() {
416         let (glean, _t) = new_glean(None);
417         let metric = CustomDistributionMetric::new(
418             CommonMetricData {
419                 name: "distribution".into(),
420                 category: "telemetry".into(),
421                 send_in_pings: vec!["store1".into()],
422                 disabled: false,
423                 lifetime: Lifetime::Ping,
424                 ..Default::default()
425             },
426             1,
427             100,
428             10,
429             HistogramType::Exponential,
430         );
431 
432         metric.accumulate_samples_signed(&glean, vec![50]);
433 
434         let snapshot = metric.test_get_value_as_json_string(&glean, "store1");
435         assert!(snapshot.is_some());
436     }
437 }
438