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