1use std::cmp::Ordering;
6use std::collections::hash_map::Entry;
7use std::collections::HashMap;
8use std::fs::{create_dir_all, File, OpenOptions};
9use std::io::BufReader;
10use std::io::Write;
11use std::io::{self, BufRead};
12use std::path::{Path, PathBuf};
13use std::sync::{atomic, Arc, Mutex, RwLock};
14use std::{fs, mem};
15
16use chrono::{DateTime, FixedOffset, Utc};
17
18use malloc_size_of::MallocSizeOf;
19use malloc_size_of_derive::MallocSizeOf;
20use serde::{Deserialize, Serialize};
21use serde_json::{json, Value as JsonValue};
22
23use crate::common_metric_data::CommonMetricDataInternal;
24use crate::error_recording::{record_error, ErrorType};
25use crate::metrics::{DatetimeMetric, TimeUnit};
26use crate::session::{EventSessionContext, SessionMetadata};
27use crate::storage::INTERNAL_STORAGE;
28use crate::util::get_iso_time_string;
29use crate::Glean;
30use crate::Result;
31use crate::{CommonMetricData, CounterMetric, Lifetime};
32
33#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, MallocSizeOf)]
35#[cfg_attr(test, derive(Default))]
36pub struct RecordedEvent {
37 pub timestamp: u64,
41
42 pub category: String,
46
47 pub name: String,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
56 pub extra: Option<HashMap<String, String>>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
63 #[serde(default)]
64 pub session: Option<SessionMetadata>,
65}
66
67#[derive(
69 Debug, Clone, Deserialize, Serialize, PartialEq, Eq, malloc_size_of_derive::MallocSizeOf,
70)]
71struct StoredEvent {
72 #[serde(flatten)]
73 event: RecordedEvent,
74
75 #[serde(default)]
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub execution_counter: Option<i32>,
82}
83
84#[derive(Debug)]
106pub struct EventDatabase {
107 pub path: PathBuf,
109 event_stores: RwLock<HashMap<String, Vec<StoredEvent>>>,
111 event_store_files: RwLock<HashMap<String, Arc<File>>>,
112 file_lock: Mutex<()>,
114 events_pings_submitted: atomic::AtomicUsize,
117}
118
119impl MallocSizeOf for EventDatabase {
120 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
121 let mut n = 0;
122 n += self.event_stores.read().unwrap().size_of(ops);
123
124 let map = self.event_store_files.read().unwrap();
125 for store_name in map.keys() {
126 n += store_name.size_of(ops);
127 n += mem::size_of::<File>();
129 }
130 n
131 }
132}
133
134impl EventDatabase {
135 pub fn new(data_path: &Path) -> Result<Self> {
142 let path = data_path.join("events");
143 create_dir_all(&path)?;
144
145 Ok(Self {
146 path,
147 event_stores: RwLock::new(HashMap::new()),
148 event_store_files: RwLock::new(HashMap::new()),
149 file_lock: Mutex::new(()),
150 events_pings_submitted: atomic::AtomicUsize::new(0),
151 })
152 }
153
154 pub fn flush_pending_events_on_startup(
180 &self,
181 glean: &Glean,
182 trim_data_to_registered_pings: bool,
183 ) -> bool {
184 match self.load_events_from_disk(glean, trim_data_to_registered_pings) {
185 Ok(_) => {
186 let stores_with_events: Vec<String> = {
187 self.event_stores
188 .read()
189 .unwrap()
190 .keys()
191 .map(|x| x.to_owned())
192 .collect() };
194 let has_events_events = stores_with_events.contains(&"events".to_owned());
197 let glean_restarted_stores = if has_events_events {
198 stores_with_events
199 .into_iter()
200 .filter(|store| store != "events")
201 .collect()
202 } else {
203 stores_with_events
204 };
205 if !glean_restarted_stores.is_empty() {
206 for store_name in glean_restarted_stores.iter() {
207 CounterMetric::new(CommonMetricData {
208 name: "execution_counter".into(),
209 category: store_name.into(),
210 send_in_pings: vec![INTERNAL_STORAGE.into()],
211 lifetime: Lifetime::Ping,
212 ..Default::default()
213 })
214 .add_sync(glean, 1);
215 }
216 let glean_restarted = CommonMetricData {
217 name: "restarted".into(),
218 category: "glean".into(),
219 send_in_pings: glean_restarted_stores,
220 lifetime: Lifetime::Ping,
221 ..Default::default()
222 };
223 let startup = get_iso_time_string(glean.start_time(), TimeUnit::Minute);
224 let mut extra: HashMap<String, String> =
225 [("glean.startup.date".into(), startup)].into();
226 if glean.with_timestamps() {
227 let now = Utc::now();
228 let precise_timestamp = now.timestamp_millis() as u64;
229 extra.insert("glean_timestamp".to_string(), precise_timestamp.to_string());
230 }
231 self.record(
232 glean,
233 &glean_restarted.into(),
234 crate::get_timestamp_ms(),
235 Some(extra),
236 EventSessionContext::OutOfSession,
237 );
238 }
239 if has_events_events && glean.submit_ping_by_name("events", Some("startup")) {
240 self.events_pings_submitted
241 .fetch_sub(1, atomic::Ordering::Relaxed);
242 true
243 } else {
244 false
245 }
246 }
247 Err(err) => {
248 log::warn!("Error loading events from disk: {}", err);
249 false
250 }
251 }
252 }
253
254 fn load_events_from_disk(
255 &self,
256 glean: &Glean,
257 trim_data_to_registered_pings: bool,
258 ) -> Result<()> {
259 let mut db = self.event_stores.write().unwrap(); let _lock = self.file_lock.lock().unwrap(); for entry in fs::read_dir(&self.path)? {
267 let entry = entry?;
268 if entry.file_type()?.is_file() {
269 let store_name = entry.file_name().into_string()?;
270 log::info!("Loading events for {}", store_name);
271 if trim_data_to_registered_pings && glean.get_ping_by_name(&store_name).is_none() {
272 log::warn!("Trimming {}'s events", store_name);
273 if let Err(err) = fs::remove_file(entry.path()) {
274 match err.kind() {
275 std::io::ErrorKind::NotFound => {
276 }
278 _ => log::warn!("Error trimming events file '{}': {}", store_name, err),
279 }
280 }
281 continue;
282 }
283 let file = BufReader::new(File::open(entry.path())?);
284 db.insert(
285 store_name,
286 file.lines()
287 .map_while(Result::ok)
288 .filter_map(|line| serde_json::from_str::<StoredEvent>(&line).ok())
289 .collect(),
290 );
291 }
292 }
293 Ok(())
294 }
295
296 pub fn record(
315 &self,
316 glean: &Glean,
317 meta: &CommonMetricDataInternal,
318 timestamp: u64,
319 extra: Option<HashMap<String, String>>,
320 ctx: EventSessionContext,
321 ) -> bool {
322 if !glean.is_upload_enabled() {
324 return false;
325 }
326
327 let session = match ctx {
329 EventSessionContext::OutOfSession => None,
330 EventSessionContext::InSession(session_meta) => Some(session_meta),
331 };
332
333 let mut submit_max_capacity_event_ping = false;
334 {
335 let mut db = self.event_stores.write().unwrap(); for store_name in meta.inner.send_in_pings.iter() {
337 if !glean.is_ping_enabled(store_name) {
338 continue;
339 }
340
341 let store = db.entry(store_name.to_string()).or_default();
342 let execution_counter = CounterMetric::new(CommonMetricData {
343 name: "execution_counter".into(),
344 category: store_name.into(),
345 send_in_pings: vec![INTERNAL_STORAGE.into()],
346 lifetime: Lifetime::Ping,
347 ..Default::default()
348 })
349 .get_value(glean, INTERNAL_STORAGE);
350 let event = StoredEvent {
352 event: RecordedEvent {
353 timestamp,
354 category: meta.inner.category.to_string(),
355 name: meta.inner.name.to_string(),
356 extra: extra.clone(),
357 session: session.clone(),
358 },
359 execution_counter,
360 };
361 let event_json = serde_json::to_string(&event).unwrap(); store.push(event);
363 self.write_event_to_disk(store_name, &event_json);
364 if store_name == "events" {
365 if store.len() == glean.get_max_events() {
366 submit_max_capacity_event_ping = true;
367 }
368 let factor = glean.get_events_ping_acceleration_factor();
369 let events_pings_submitted =
370 self.events_pings_submitted.load(atomic::Ordering::Relaxed);
371 if factor > events_pings_submitted {
372 let a = glean.get_max_events() / factor.saturating_mul(factor);
374 let x = events_pings_submitted + 1; let y = a.saturating_mul(x.saturating_mul(x));
376 if store.len() >= y {
379 submit_max_capacity_event_ping = true;
380 }
381 }
382 }
383 }
384 }
385 if submit_max_capacity_event_ping {
386 glean.submit_ping_by_name("events", Some("max_capacity"));
387 true
388 } else {
389 false
390 }
391 }
392
393 fn get_event_store(&self, store_name: &str) -> Result<Arc<File>, io::Error> {
394 let mut map = self.event_store_files.write().unwrap();
396 let entry = map.entry(store_name.to_string());
397
398 match entry {
399 Entry::Occupied(entry) => Ok(Arc::clone(entry.get())),
400 Entry::Vacant(entry) => {
401 let file = OpenOptions::new()
402 .create(true)
403 .append(true)
404 .open(self.path.join(store_name))?;
405 let file = Arc::new(file);
406 let entry = entry.insert(file);
407 Ok(Arc::clone(entry))
408 }
409 }
410 }
411
412 fn write_event_to_disk(&self, store_name: &str, event_json: &str) {
419 let _lock = self.file_lock.lock().unwrap(); let write_res = (|| {
422 let mut file = self.get_event_store(store_name)?;
423 file.write_all(event_json.as_bytes())?;
424 file.write_all(b"\n")?;
425 file.flush()?;
426 Ok::<(), std::io::Error>(())
427 })();
428
429 if let Err(err) = write_res {
430 log::warn!("IO error writing event to store '{}': {}", store_name, err);
431 }
432 }
433
434 fn normalize_store(
463 &self,
464 glean: &Glean,
465 store_name: &str,
466 store: &mut Vec<StoredEvent>,
467 glean_start_time: DateTime<FixedOffset>,
468 ) {
469 let is_glean_restarted =
470 |event: &RecordedEvent| event.category == "glean" && event.name == "restarted";
471 let glean_restarted_meta = |store_name: &str| CommonMetricData {
472 name: "restarted".into(),
473 category: "glean".into(),
474 send_in_pings: vec![store_name.into()],
475 lifetime: Lifetime::Ping,
476 ..Default::default()
477 };
478 store.sort_by(|a, b| {
480 a.execution_counter
481 .cmp(&b.execution_counter)
482 .then_with(|| a.event.timestamp.cmp(&b.event.timestamp))
483 .then_with(|| {
484 if is_glean_restarted(&a.event) {
485 Ordering::Less
486 } else {
487 Ordering::Greater
488 }
489 })
490 });
491 let final_event = match store
495 .iter()
496 .rposition(|event| !is_glean_restarted(&event.event))
497 {
498 Some(idx) => idx + 1,
499 _ => 0,
500 };
501 store.drain(final_event..);
502 let first_event = store
503 .iter()
504 .position(|event| !is_glean_restarted(&event.event))
505 .unwrap_or(store.len());
506 store.drain(..first_event);
507 if store.is_empty() {
508 return;
510 }
511 let mut cur_ec = 0;
517 let mut intra_group_offset = store[0].event.timestamp;
519 let mut inter_group_offset = 0;
521 let mut highest_ts = 0;
522 for event in store.iter_mut() {
523 let execution_counter = event.execution_counter.take().unwrap_or(0);
524 if is_glean_restarted(&event.event) {
525 cur_ec = execution_counter;
528 let glean_startup_date = event
529 .event
530 .extra
531 .as_mut()
532 .and_then(|extra| {
533 extra.remove("glean.startup.date").and_then(|date_str| {
534 DateTime::parse_from_str(&date_str, TimeUnit::Minute.format_pattern())
535 .map_err(|_| {
536 record_error(
537 glean,
538 &glean_restarted_meta(store_name).into(),
539 ErrorType::InvalidState,
540 format!("Unparseable glean.startup.date '{}'", date_str),
541 None,
542 );
543 })
544 .ok()
545 })
546 })
547 .unwrap_or(glean_start_time);
548 if event
549 .event
550 .extra
551 .as_ref()
552 .is_some_and(|extra| extra.is_empty())
553 {
554 event.event.extra = None;
556 }
557 let ping_start = DatetimeMetric::new(
558 CommonMetricData {
559 name: format!("{}#start", store_name),
560 category: "".into(),
561 send_in_pings: vec![INTERNAL_STORAGE.into()],
562 lifetime: Lifetime::User,
563 ..Default::default()
564 },
565 TimeUnit::Minute,
566 );
567 let ping_start = ping_start
568 .get_value(glean, INTERNAL_STORAGE)
569 .unwrap_or(glean_start_time);
570 let time_from_ping_start_to_glean_restarted =
571 (glean_startup_date - ping_start).num_milliseconds();
572 intra_group_offset = event.event.timestamp;
573 inter_group_offset =
574 u64::try_from(time_from_ping_start_to_glean_restarted).unwrap_or(0);
575 if inter_group_offset < highest_ts {
576 record_error(
577 glean,
578 &glean_restarted_meta(store_name).into(),
579 ErrorType::InvalidValue,
580 format!("Time between restart and ping start {} indicates client clock weirdness.", time_from_ping_start_to_glean_restarted),
581 None,
582 );
583 inter_group_offset = highest_ts + 1;
588 }
589 } else if cur_ec == 0 {
590 cur_ec = execution_counter;
592 }
593 event.event.timestamp = event.event.timestamp - intra_group_offset + inter_group_offset;
594 if execution_counter != cur_ec {
595 record_error(
596 glean,
597 &glean_restarted_meta(store_name).into(),
598 ErrorType::InvalidState,
599 format!(
600 "Inconsistent execution counter {} (expected {})",
601 execution_counter, cur_ec
602 ),
603 None,
604 );
605 cur_ec = execution_counter;
607 }
608
609 if event.event.timestamp > i64::MAX as u64 {
612 glean
613 .additional_metrics
614 .event_timestamp_clamped
615 .add_sync(glean, 1);
616 log::warn!(
617 "Calculated event timestamp was too high. Got: {}, max: {}",
618 event.event.timestamp,
619 i64::MAX,
620 );
621 event.event.timestamp = event.event.timestamp.clamp(0, i64::MAX as u64);
622 }
623
624 if highest_ts > event.event.timestamp {
625 record_error(
628 glean,
629 &glean_restarted_meta(store_name).into(),
630 ErrorType::InvalidState,
631 format!(
632 "Inconsistent previous highest timestamp {} (expected <= {})",
633 highest_ts, event.event.timestamp
634 ),
635 None,
636 );
637 }
639 highest_ts = event.event.timestamp
640 }
641 }
642
643 pub fn snapshot_as_json(
655 &self,
656 glean: &Glean,
657 store_name: &str,
658 clear_store: bool,
659 ) -> Option<JsonValue> {
660 let result = {
661 let mut db = self.event_stores.write().unwrap(); db.get_mut(&store_name.to_string()).and_then(|store| {
663 if !store.is_empty() {
664 let mut clone;
667 let store = if clear_store {
668 store
669 } else {
670 clone = store.clone();
671 &mut clone
672 };
673 self.normalize_store(glean, store_name, store, glean.start_time());
675 Some(json!(store))
676 } else {
677 log::warn!("Unexpectly got empty event store for '{}'", store_name);
678 None
679 }
680 })
681 };
682
683 if clear_store {
684 self.event_stores
685 .write()
686 .unwrap() .remove(&store_name.to_string());
688 self.event_store_files
689 .write()
690 .unwrap() .remove(&store_name.to_string());
692
693 let _lock = self.file_lock.lock().unwrap(); if let Err(err) = fs::remove_file(self.path.join(store_name)) {
695 match err.kind() {
696 std::io::ErrorKind::NotFound => {
697 }
699 _ => log::warn!("Error removing events queue file '{}': {}", store_name, err),
700 }
701 }
702 }
703
704 if clear_store && store_name == "events" {
705 self.events_pings_submitted
706 .fetch_add(1, atomic::Ordering::Relaxed);
707 }
708
709 result
710 }
711
712 pub fn clear_all(&self) -> Result<()> {
714 self.event_stores.write().unwrap().clear();
716 self.event_store_files.write().unwrap().clear();
717
718 let _lock = self.file_lock.lock().unwrap();
720 std::fs::remove_dir_all(&self.path)?;
721 create_dir_all(&self.path)?;
722
723 Ok(())
724 }
725
726 pub fn test_get_value<'a>(
733 &'a self,
734 meta: &'a CommonMetricDataInternal,
735 store_name: &str,
736 ) -> Option<Vec<RecordedEvent>> {
737 let value: Vec<RecordedEvent> = self
738 .event_stores
739 .read()
740 .unwrap() .get(&store_name.to_string())
742 .into_iter()
743 .flatten()
744 .map(|stored_event| stored_event.event.clone())
745 .filter(|event| event.name == meta.inner.name && event.category == meta.inner.category)
746 .collect();
747 if !value.is_empty() {
748 Some(value)
749 } else {
750 None
751 }
752 }
753}
754
755#[cfg(test)]
756mod test {
757 use super::*;
758 use crate::metrics::RemoteSettingsConfig;
759 use crate::test_get_num_recorded_errors;
760 use crate::tests::new_glean;
761 use chrono::{TimeZone, Timelike};
762
763 #[test]
764 fn handle_truncated_events_on_disk() {
765 let (glean, t) = new_glean(None);
766
767 {
768 let db = EventDatabase::new(t.path()).unwrap();
769 db.write_event_to_disk("events", "{\"timestamp\": 500");
770 db.write_event_to_disk("events", "{\"timestamp\"");
771 db.write_event_to_disk(
772 "events",
773 "{\"timestamp\": 501, \"category\": \"ui\", \"name\": \"click\"}",
774 );
775 }
776
777 {
778 let db = EventDatabase::new(t.path()).unwrap();
779 db.load_events_from_disk(&glean, false).unwrap();
780 let events = &db.event_stores.read().unwrap()["events"];
781 assert_eq!(1, events.len());
782 }
783 }
784
785 #[test]
786 fn stable_serialization() {
787 let event_empty = RecordedEvent {
788 timestamp: 2,
789 category: "cat".to_string(),
790 name: "name".to_string(),
791 extra: None,
792 session: None,
793 };
794
795 let mut data = HashMap::new();
796 data.insert("a key".to_string(), "a value".to_string());
797 let event_data = RecordedEvent {
798 timestamp: 2,
799 category: "cat".to_string(),
800 name: "name".to_string(),
801 extra: Some(data),
802 session: None,
803 };
804
805 let event_empty_json = ::serde_json::to_string_pretty(&event_empty).unwrap();
806 let event_data_json = ::serde_json::to_string_pretty(&event_data).unwrap();
807
808 assert_eq!(
809 StoredEvent {
810 event: event_empty,
811 execution_counter: None
812 },
813 serde_json::from_str(&event_empty_json).unwrap()
814 );
815 assert_eq!(
816 StoredEvent {
817 event: event_data,
818 execution_counter: None
819 },
820 serde_json::from_str(&event_data_json).unwrap()
821 );
822 }
823
824 #[test]
825 fn deserialize_existing_data() {
826 let event_empty_json = r#"
827{
828 "timestamp": 2,
829 "category": "cat",
830 "name": "name"
831}
832 "#;
833
834 let event_data_json = r#"
835{
836 "timestamp": 2,
837 "category": "cat",
838 "name": "name",
839 "extra": {
840 "a key": "a value"
841 }
842}
843 "#;
844
845 let event_empty = RecordedEvent {
846 timestamp: 2,
847 category: "cat".to_string(),
848 name: "name".to_string(),
849 extra: None,
850 session: None,
851 };
852
853 let mut data = HashMap::new();
854 data.insert("a key".to_string(), "a value".to_string());
855 let event_data = RecordedEvent {
856 timestamp: 2,
857 category: "cat".to_string(),
858 name: "name".to_string(),
859 extra: Some(data),
860 session: None,
861 };
862
863 assert_eq!(
864 StoredEvent {
865 event: event_empty,
866 execution_counter: None
867 },
868 serde_json::from_str(event_empty_json).unwrap()
869 );
870 assert_eq!(
871 StoredEvent {
872 event: event_data,
873 execution_counter: None
874 },
875 serde_json::from_str(event_data_json).unwrap()
876 );
877 }
878
879 #[test]
880 fn doesnt_record_when_upload_is_disabled() {
881 let (mut glean, dir) = new_glean(None);
882 let db = EventDatabase::new(dir.path()).unwrap();
883
884 let test_storage = "store1";
885 let test_category = "category";
886 let test_name = "name";
887 let test_timestamp = 2;
888 let test_meta = CommonMetricDataInternal::new(test_category, test_name, test_storage);
889 let event_data = RecordedEvent {
890 timestamp: test_timestamp,
891 category: test_category.to_string(),
892 name: test_name.to_string(),
893 extra: None,
894 session: None,
895 };
896
897 db.record(
900 &glean,
901 &test_meta,
902 2,
903 None,
904 EventSessionContext::OutOfSession,
905 );
906 {
907 let event_stores = db.event_stores.read().unwrap();
908 assert_eq!(
909 &StoredEvent {
910 event: event_data,
911 execution_counter: None
912 },
913 &event_stores.get(test_storage).unwrap()[0]
914 );
915 assert_eq!(event_stores.get(test_storage).unwrap().len(), 1);
916 }
917
918 glean.set_upload_enabled(false);
919
920 db.record(
922 &glean,
923 &test_meta,
924 2,
925 None,
926 EventSessionContext::OutOfSession,
927 );
928 {
929 let event_stores = db.event_stores.read().unwrap();
930 assert_eq!(event_stores.get(test_storage).unwrap().len(), 1);
931 }
932 }
933
934 #[test]
935 fn normalize_store_of_glean_restarted() {
936 let (glean, _dir) = new_glean(None);
938
939 let store_name = "store-name";
940 let glean_restarted = StoredEvent {
941 event: RecordedEvent {
942 timestamp: 2,
943 category: "glean".into(),
944 name: "restarted".into(),
945 extra: None,
946 session: None,
947 },
948 execution_counter: None,
949 };
950 let mut store = vec![glean_restarted.clone()];
951 let glean_start_time = glean.start_time();
952
953 glean
954 .event_storage()
955 .normalize_store(&glean, store_name, &mut store, glean_start_time);
956 assert!(store.is_empty());
957
958 let mut store = vec![glean_restarted.clone(), glean_restarted.clone()];
959 glean
960 .event_storage()
961 .normalize_store(&glean, store_name, &mut store, glean_start_time);
962 assert!(store.is_empty());
963
964 let mut store = vec![
965 glean_restarted.clone(),
966 glean_restarted.clone(),
967 glean_restarted,
968 ];
969 glean
970 .event_storage()
971 .normalize_store(&glean, store_name, &mut store, glean_start_time);
972 assert!(store.is_empty());
973 }
974
975 #[test]
976 fn normalize_store_of_glean_restarted_on_both_ends() {
977 let (glean, _dir) = new_glean(None);
979
980 let store_name = "store-name";
981 let glean_restarted = StoredEvent {
982 event: RecordedEvent {
983 timestamp: 2,
984 category: "glean".into(),
985 name: "restarted".into(),
986 extra: None,
987 session: None,
988 },
989 execution_counter: None,
990 };
991 let not_glean_restarted = StoredEvent {
992 event: RecordedEvent {
993 timestamp: 20,
994 category: "category".into(),
995 name: "name".into(),
996 extra: None,
997 session: None,
998 },
999 execution_counter: None,
1000 };
1001 let mut store = vec![
1002 glean_restarted.clone(),
1003 not_glean_restarted.clone(),
1004 glean_restarted,
1005 ];
1006 let glean_start_time = glean.start_time();
1007
1008 glean
1009 .event_storage()
1010 .normalize_store(&glean, store_name, &mut store, glean_start_time);
1011 assert_eq!(1, store.len());
1012 assert_eq!(
1013 StoredEvent {
1014 event: RecordedEvent {
1015 timestamp: 0,
1016 ..not_glean_restarted.event
1017 },
1018 execution_counter: None
1019 },
1020 store[0]
1021 );
1022 }
1023
1024 #[test]
1025 fn normalize_store_single_run_timestamp_math() {
1026 let (glean, _dir) = new_glean(None);
1030
1031 let store_name = "store-name";
1032 let glean_restarted = StoredEvent {
1033 event: RecordedEvent {
1034 timestamp: 2,
1035 category: "glean".into(),
1036 name: "restarted".into(),
1037 extra: None,
1038 session: None,
1039 },
1040 execution_counter: None,
1041 };
1042 let timestamps = [20, 40, 200];
1043 let not_glean_restarted = StoredEvent {
1044 event: RecordedEvent {
1045 timestamp: timestamps[0],
1046 category: "category".into(),
1047 name: "name".into(),
1048 extra: None,
1049 session: None,
1050 },
1051 execution_counter: None,
1052 };
1053 let mut store = vec![
1054 glean_restarted.clone(),
1055 not_glean_restarted.clone(),
1056 StoredEvent {
1057 event: RecordedEvent {
1058 timestamp: timestamps[1],
1059 ..not_glean_restarted.event.clone()
1060 },
1061 execution_counter: None,
1062 },
1063 StoredEvent {
1064 event: RecordedEvent {
1065 timestamp: timestamps[2],
1066 ..not_glean_restarted.event.clone()
1067 },
1068 execution_counter: None,
1069 },
1070 glean_restarted,
1071 ];
1072
1073 glean
1074 .event_storage()
1075 .normalize_store(&glean, store_name, &mut store, glean.start_time());
1076 assert_eq!(3, store.len());
1077 for (timestamp, event) in timestamps.iter().zip(store.iter()) {
1078 assert_eq!(
1079 &StoredEvent {
1080 event: RecordedEvent {
1081 timestamp: timestamp - timestamps[0],
1082 ..not_glean_restarted.clone().event
1083 },
1084 execution_counter: None
1085 },
1086 event
1087 );
1088 }
1089 }
1090
1091 #[test]
1092 fn normalize_store_multi_run_timestamp_math() {
1093 let (glean, _dir) = new_glean(None);
1098
1099 let store_name = "store-name";
1100 let glean_restarted = StoredEvent {
1101 event: RecordedEvent {
1102 category: "glean".into(),
1103 name: "restarted".into(),
1104 ..Default::default()
1105 },
1106 execution_counter: None,
1107 };
1108 let not_glean_restarted = StoredEvent {
1109 event: RecordedEvent {
1110 category: "category".into(),
1111 name: "name".into(),
1112 ..Default::default()
1113 },
1114 execution_counter: None,
1115 };
1116
1117 let timestamps = [20, 40, 200, 12];
1120 let ecs = [0, 1];
1121 let some_hour = 16;
1122 let startup_date = FixedOffset::east_opt(0)
1123 .unwrap()
1124 .with_ymd_and_hms(2022, 11, 24, some_hour, 29, 0) .unwrap();
1126 let glean_start_time = startup_date.with_hour(some_hour - 1);
1127 let restarted_ts = 2;
1128 let mut store = vec![
1129 StoredEvent {
1130 event: RecordedEvent {
1131 timestamp: timestamps[0],
1132 ..not_glean_restarted.event.clone()
1133 },
1134 execution_counter: Some(ecs[0]),
1135 },
1136 StoredEvent {
1137 event: RecordedEvent {
1138 timestamp: timestamps[1],
1139 ..not_glean_restarted.event.clone()
1140 },
1141 execution_counter: Some(ecs[0]),
1142 },
1143 StoredEvent {
1144 event: RecordedEvent {
1145 timestamp: timestamps[2],
1146 ..not_glean_restarted.event.clone()
1147 },
1148 execution_counter: Some(ecs[0]),
1149 },
1150 StoredEvent {
1151 event: RecordedEvent {
1152 extra: Some(
1153 [(
1154 "glean.startup.date".into(),
1155 get_iso_time_string(startup_date, TimeUnit::Minute),
1156 )]
1157 .into(),
1158 ),
1159 timestamp: restarted_ts,
1160 ..glean_restarted.event.clone()
1161 },
1162 execution_counter: Some(ecs[1]),
1163 },
1164 StoredEvent {
1165 event: RecordedEvent {
1166 timestamp: timestamps[3],
1167 ..not_glean_restarted.event.clone()
1168 },
1169 execution_counter: Some(ecs[1]),
1170 },
1171 ];
1172
1173 glean.event_storage().normalize_store(
1174 &glean,
1175 store_name,
1176 &mut store,
1177 glean_start_time.unwrap(),
1178 );
1179 assert_eq!(5, store.len()); for (timestamp, event) in timestamps[..timestamps.len() - 1].iter().zip(store.clone()) {
1183 assert_eq!(
1184 StoredEvent {
1185 event: RecordedEvent {
1186 timestamp: timestamp - timestamps[0],
1187 ..not_glean_restarted.event.clone()
1188 },
1189 execution_counter: None,
1190 },
1191 event
1192 );
1193 }
1194 let hour_in_millis = 3600000;
1196 assert_eq!(
1197 store[3],
1198 StoredEvent {
1199 event: RecordedEvent {
1200 timestamp: hour_in_millis,
1201 ..glean_restarted.event
1202 },
1203 execution_counter: None,
1204 }
1205 );
1206 assert_eq!(
1208 store[4],
1209 StoredEvent {
1210 event: RecordedEvent {
1211 timestamp: hour_in_millis + timestamps[3] - restarted_ts,
1212 ..not_glean_restarted.event
1213 },
1214 execution_counter: None,
1215 }
1216 );
1217 }
1218
1219 #[test]
1220 fn normalize_store_multi_run_client_clocks() {
1221 let (glean, _dir) = new_glean(None);
1224
1225 let store_name = "store-name";
1226 let glean_restarted = StoredEvent {
1227 event: RecordedEvent {
1228 category: "glean".into(),
1229 name: "restarted".into(),
1230 ..Default::default()
1231 },
1232 execution_counter: None,
1233 };
1234 let not_glean_restarted = StoredEvent {
1235 event: RecordedEvent {
1236 category: "category".into(),
1237 name: "name".into(),
1238 ..Default::default()
1239 },
1240 execution_counter: None,
1241 };
1242
1243 let timestamps = [20, 40, 12, 200];
1246 let ecs = [0, 1];
1247 let some_hour = 10;
1248 let startup_date = FixedOffset::east_opt(0)
1249 .unwrap()
1250 .with_ymd_and_hms(2022, 11, 25, some_hour, 37, 0) .unwrap();
1252 let glean_start_time = startup_date.with_hour(some_hour + 1);
1253 let restarted_ts = 2;
1254 let mut store = vec![
1255 StoredEvent {
1256 event: RecordedEvent {
1257 timestamp: timestamps[0],
1258 ..not_glean_restarted.event.clone()
1259 },
1260 execution_counter: Some(ecs[0]),
1261 },
1262 StoredEvent {
1263 event: RecordedEvent {
1264 timestamp: timestamps[1],
1265 ..not_glean_restarted.event.clone()
1266 },
1267 execution_counter: Some(ecs[0]),
1268 },
1269 StoredEvent {
1270 event: RecordedEvent {
1271 extra: Some(
1272 [(
1273 "glean.startup.date".into(),
1274 get_iso_time_string(startup_date, TimeUnit::Minute),
1275 )]
1276 .into(),
1277 ),
1278 timestamp: restarted_ts,
1279 ..glean_restarted.event.clone()
1280 },
1281 execution_counter: Some(ecs[1]),
1282 },
1283 StoredEvent {
1284 event: RecordedEvent {
1285 timestamp: timestamps[2],
1286 ..not_glean_restarted.event.clone()
1287 },
1288 execution_counter: Some(ecs[1]),
1289 },
1290 StoredEvent {
1291 event: RecordedEvent {
1292 timestamp: timestamps[3],
1293 ..not_glean_restarted.event.clone()
1294 },
1295 execution_counter: Some(ecs[1]),
1296 },
1297 ];
1298
1299 glean.event_storage().normalize_store(
1300 &glean,
1301 store_name,
1302 &mut store,
1303 glean_start_time.unwrap(),
1304 );
1305 assert_eq!(5, store.len()); for (timestamp, event) in timestamps[..timestamps.len() - 2].iter().zip(store.clone()) {
1309 assert_eq!(
1310 StoredEvent {
1311 event: RecordedEvent {
1312 timestamp: timestamp - timestamps[0],
1313 ..not_glean_restarted.event.clone()
1314 },
1315 execution_counter: None,
1316 },
1317 event
1318 );
1319 }
1320 assert_eq!(
1324 store[2],
1325 StoredEvent {
1326 event: RecordedEvent {
1327 timestamp: store[1].event.timestamp + 1,
1328 ..glean_restarted.event
1329 },
1330 execution_counter: None,
1331 }
1332 );
1333 assert_eq!(
1335 store[3],
1336 StoredEvent {
1337 event: RecordedEvent {
1338 timestamp: timestamps[2] - restarted_ts + store[2].event.timestamp,
1339 ..not_glean_restarted.event
1340 },
1341 execution_counter: None,
1342 }
1343 );
1344 assert_eq!(
1346 Ok(1),
1347 test_get_num_recorded_errors(
1348 &glean,
1349 &CommonMetricData {
1350 name: "restarted".into(),
1351 category: "glean".into(),
1352 send_in_pings: vec![store_name.into()],
1353 lifetime: Lifetime::Ping,
1354 ..Default::default()
1355 }
1356 .into(),
1357 ErrorType::InvalidValue
1358 )
1359 );
1360 }
1361
1362 #[test]
1363 fn normalize_store_non_zero_ec() {
1364 let (glean, _dir) = new_glean(None);
1367
1368 let store_name = "store-name";
1369 let glean_restarted = StoredEvent {
1370 event: RecordedEvent {
1371 timestamp: 2,
1372 category: "glean".into(),
1373 name: "restarted".into(),
1374 extra: None,
1375 session: None,
1376 },
1377 execution_counter: Some(2),
1378 };
1379 let not_glean_restarted = StoredEvent {
1380 event: RecordedEvent {
1381 timestamp: 20,
1382 category: "category".into(),
1383 name: "name".into(),
1384 extra: None,
1385 session: None,
1386 },
1387 execution_counter: Some(2),
1388 };
1389 let glean_restarted_2 = StoredEvent {
1390 event: RecordedEvent {
1391 timestamp: 2,
1392 category: "glean".into(),
1393 name: "restarted".into(),
1394 extra: None,
1395 session: None,
1396 },
1397 execution_counter: Some(3),
1398 };
1399 let mut store = vec![
1400 glean_restarted,
1401 not_glean_restarted.clone(),
1402 glean_restarted_2,
1403 ];
1404 let glean_start_time = glean.start_time();
1405
1406 glean
1407 .event_storage()
1408 .normalize_store(&glean, store_name, &mut store, glean_start_time);
1409
1410 assert_eq!(1, store.len());
1411 assert_eq!(
1412 StoredEvent {
1413 event: RecordedEvent {
1414 timestamp: 0,
1415 ..not_glean_restarted.event
1416 },
1417 execution_counter: None
1418 },
1419 store[0]
1420 );
1421 assert!(test_get_num_recorded_errors(
1423 &glean,
1424 &CommonMetricData {
1425 name: "restarted".into(),
1426 category: "glean".into(),
1427 send_in_pings: vec![store_name.into()],
1428 lifetime: Lifetime::Ping,
1429 ..Default::default()
1430 }
1431 .into(),
1432 ErrorType::InvalidState
1433 )
1434 .is_err());
1435 assert!(test_get_num_recorded_errors(
1437 &glean,
1438 &CommonMetricData {
1439 name: "restarted".into(),
1440 category: "glean".into(),
1441 send_in_pings: vec![store_name.into()],
1442 lifetime: Lifetime::Ping,
1443 ..Default::default()
1444 }
1445 .into(),
1446 ErrorType::InvalidValue
1447 )
1448 .is_err());
1449 }
1450
1451 #[test]
1452 fn normalize_store_clamps_timestamp() {
1453 let (glean, _dir) = new_glean(None);
1454
1455 let store_name = "store-name";
1456 let event = RecordedEvent {
1457 category: "category".into(),
1458 name: "name".into(),
1459 ..Default::default()
1460 };
1461
1462 let timestamps = [
1463 0,
1464 (i64::MAX / 2) as u64,
1465 i64::MAX as _,
1466 (i64::MAX as u64) + 1,
1467 ];
1468 let mut store = timestamps
1469 .into_iter()
1470 .map(|timestamp| StoredEvent {
1471 event: RecordedEvent {
1472 timestamp,
1473 ..event.clone()
1474 },
1475 execution_counter: None,
1476 })
1477 .collect();
1478
1479 let glean_start_time = glean.start_time();
1480 glean
1481 .event_storage()
1482 .normalize_store(&glean, store_name, &mut store, glean_start_time);
1483 assert_eq!(4, store.len());
1484
1485 assert_eq!(0, store[0].event.timestamp);
1486 assert_eq!((i64::MAX / 2) as u64, store[1].event.timestamp);
1487 assert_eq!((i64::MAX as u64), store[2].event.timestamp);
1488 assert_eq!((i64::MAX as u64), store[3].event.timestamp);
1489 }
1490
1491 #[test]
1492 fn normalize_store_clamps_timestamp_metric_enabled() {
1493 let (glean, _dir) = new_glean(None);
1494
1495 let mut cfg = RemoteSettingsConfig::default();
1496 cfg.metrics_enabled
1497 .insert("glean.error.event_timestamp_clamped".to_string(), true);
1498 glean.apply_server_knobs_config(cfg);
1499
1500 let store_name = "store-name";
1501 let event = RecordedEvent {
1502 category: "category".into(),
1503 name: "name".into(),
1504 ..Default::default()
1505 };
1506
1507 let timestamps = [0, (i64::MAX as u64) + 1];
1508 let mut store = timestamps
1509 .into_iter()
1510 .map(|timestamp| StoredEvent {
1511 event: RecordedEvent {
1512 timestamp,
1513 ..event.clone()
1514 },
1515 execution_counter: None,
1516 })
1517 .collect();
1518
1519 let glean_start_time = glean.start_time();
1520 glean
1521 .event_storage()
1522 .normalize_store(&glean, store_name, &mut store, glean_start_time);
1523 assert_eq!(2, store.len());
1524
1525 assert_eq!(0, store[0].event.timestamp);
1526 assert_eq!((i64::MAX as u64), store[1].event.timestamp);
1527
1528 let error_count = glean
1529 .additional_metrics
1530 .event_timestamp_clamped
1531 .get_value(&glean, "health");
1532 assert_eq!(Some(1), error_count);
1533 }
1534}