in glean-core/src/event_database/mod.rs [1092:1231]
fn normalize_store_multi_run_client_clocks() {
// With multiple runs of events (separated by `glean.restarted`),
// ensure the timestamp math works. Even when the client clock goes backwards.
let (glean, _dir) = new_glean(None);
let store_name = "store-name";
let glean_restarted = StoredEvent {
event: RecordedEvent {
category: "glean".into(),
name: "restarted".into(),
..Default::default()
},
execution_counter: None,
};
let not_glean_restarted = StoredEvent {
event: RecordedEvent {
category: "category".into(),
name: "name".into(),
..Default::default()
},
execution_counter: None,
};
// This scenario represents a run of two events followed by negative one hours between runs,
// followed by two more events.
let timestamps = [20, 40, 12, 200];
let ecs = [0, 1];
let some_hour = 10;
let startup_date = FixedOffset::east(0)
.ymd(2022, 11, 25)
.and_hms(some_hour, 37, 0); // TimeUnit::Minute -- don't put seconds
let glean_start_time = startup_date.with_hour(some_hour + 1);
let restarted_ts = 2;
let mut store = vec![
StoredEvent {
event: RecordedEvent {
timestamp: timestamps[0],
..not_glean_restarted.event.clone()
},
execution_counter: Some(ecs[0]),
},
StoredEvent {
event: RecordedEvent {
timestamp: timestamps[1],
..not_glean_restarted.event.clone()
},
execution_counter: Some(ecs[0]),
},
StoredEvent {
event: RecordedEvent {
extra: Some(
[(
"glean.startup.date".into(),
get_iso_time_string(startup_date, TimeUnit::Minute),
)]
.into(),
),
timestamp: restarted_ts,
..glean_restarted.event.clone()
},
execution_counter: Some(ecs[1]),
},
StoredEvent {
event: RecordedEvent {
timestamp: timestamps[2],
..not_glean_restarted.event.clone()
},
execution_counter: Some(ecs[1]),
},
StoredEvent {
event: RecordedEvent {
timestamp: timestamps[3],
..not_glean_restarted.event.clone()
},
execution_counter: Some(ecs[1]),
},
];
glean.event_storage().normalize_store(
&glean,
store_name,
&mut store,
glean_start_time.unwrap(),
);
assert_eq!(5, store.len()); // 4 "real" events plus 1 `glean.restarted`
// Let's check the first two.
for (timestamp, event) in timestamps[..timestamps.len() - 2].iter().zip(store.clone()) {
assert_eq!(
StoredEvent {
event: RecordedEvent {
timestamp: timestamp - timestamps[0],
..not_glean_restarted.event.clone()
},
execution_counter: None,
},
event
);
}
// The third should be a glean.restarted. Its timestamp should be
// one larger than the largest timestamp seen so far (because that's
// how we ensure monotonic timestamps when client clocks go backwards).
assert_eq!(
store[2],
StoredEvent {
event: RecordedEvent {
timestamp: store[1].event.timestamp + 1,
..glean_restarted.event
},
execution_counter: None,
}
);
// The fifth should have a timestamp based on the new origin.
assert_eq!(
store[3],
StoredEvent {
event: RecordedEvent {
timestamp: timestamps[2] - restarted_ts + store[2].event.timestamp,
..not_glean_restarted.event
},
execution_counter: None,
}
);
// And we should have an InvalidValue on glean.restarted to show for it.
assert_eq!(
Ok(1),
test_get_num_recorded_errors(
&glean,
&CommonMetricData {
name: "restarted".into(),
category: "glean".into(),
send_in_pings: vec![store_name.into()],
lifetime: Lifetime::Ping,
..Default::default()
}
.into(),
ErrorType::InvalidValue
)
);
}