Add SystemClock (#8239)

This PR adds a `SystemClock` trait for abstracting away the system
clock.

This allows us to swap out the real system clock with a
`FakeSystemClock` in the tests, thus allowing the fake passage of time.

We're using this in `Telemetry` to better mock the clock for testing
purposes.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-02-22 22:28:08 -05:00 committed by GitHub
parent cc8e3c2286
commit 0de8672044
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 213 additions and 55 deletions

View file

@ -2,6 +2,7 @@ mod event_coalescer;
use crate::TelemetrySettings;
use chrono::{DateTime, Utc};
use clock::SystemClock;
use futures::Future;
use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
use once_cell::sync::Lazy;
@ -24,6 +25,7 @@ use util::TryFutureExt;
use self::event_coalescer::EventCoalescer;
pub struct Telemetry {
clock: Arc<dyn SystemClock>,
http_client: Arc<ZedHttpClient>,
executor: BackgroundExecutor,
state: Arc<Mutex<TelemetryState>>,
@ -156,7 +158,11 @@ static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
});
impl Telemetry {
pub fn new(client: Arc<ZedHttpClient>, cx: &mut AppContext) -> Arc<Self> {
pub fn new(
clock: Arc<dyn SystemClock>,
client: Arc<ZedHttpClient>,
cx: &mut AppContext,
) -> Arc<Self> {
let release_channel =
ReleaseChannel::try_global(cx).map(|release_channel| release_channel.display_name());
@ -205,6 +211,7 @@ impl Telemetry {
// TODO: Replace all hardware stuff with nested SystemSpecs json
let this = Arc::new(Self {
clock,
http_client: client,
executor: cx.background_executor().clone(),
state,
@ -317,7 +324,8 @@ impl Telemetry {
operation,
copilot_enabled,
copilot_enabled_for_language,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event)
@ -333,7 +341,8 @@ impl Telemetry {
suggestion_id,
suggestion_accepted,
file_extension,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event)
@ -349,7 +358,8 @@ impl Telemetry {
conversation_id,
kind,
model,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event)
@ -365,7 +375,8 @@ impl Telemetry {
operation,
room_id,
channel_id,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event)
@ -375,7 +386,8 @@ impl Telemetry {
let event = Event::Cpu {
usage_as_percentage,
core_count,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event)
@ -389,24 +401,18 @@ impl Telemetry {
let event = Event::Memory {
memory_in_bytes,
virtual_memory_in_bytes,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event)
}
pub fn report_app_event(self: &Arc<Self>, operation: String) {
self.report_app_event_with_date_time(operation, Utc::now());
}
fn report_app_event_with_date_time(
self: &Arc<Self>,
operation: String,
date_time: DateTime<Utc>,
) -> Event {
pub fn report_app_event(self: &Arc<Self>, operation: String) -> Event {
let event = Event::App {
operation,
milliseconds_since_first_event: self.milliseconds_since_first_event(date_time),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event.clone());
@ -418,7 +424,8 @@ impl Telemetry {
let event = Event::Setting {
setting,
value,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event)
@ -433,7 +440,8 @@ impl Telemetry {
let event = Event::Edit {
duration: end.timestamp_millis() - start.timestamp_millis(),
environment,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event);
@ -444,7 +452,8 @@ impl Telemetry {
let event = Event::Action {
source,
action,
milliseconds_since_first_event: self.milliseconds_since_first_event(Utc::now()),
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
self.report_event(event)
@ -590,29 +599,32 @@ impl Telemetry {
mod tests {
use super::*;
use chrono::TimeZone;
use clock::FakeSystemClock;
use gpui::TestAppContext;
use util::http::FakeHttpClient;
#[gpui::test]
fn test_telemetry_flush_on_max_queue_size(cx: &mut TestAppContext) {
init_test(cx);
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let http = FakeHttpClient::with_200_response();
let installation_id = Some("installation_id".to_string());
let session_id = "session_id".to_string();
cx.update(|cx| {
let telemetry = Telemetry::new(http, cx);
let telemetry = Telemetry::new(clock.clone(), http, cx);
telemetry.state.lock().max_queue_size = 4;
telemetry.start(installation_id, session_id, cx);
assert!(is_empty_state(&telemetry));
let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap();
let first_date_time = clock.utc_now();
let operation = "test".to_string();
let event =
telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
@ -627,9 +639,9 @@ mod tests {
Some(first_date_time)
);
let mut date_time = first_date_time + chrono::Duration::milliseconds(100);
clock.advance(chrono::Duration::milliseconds(100));
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
@ -644,9 +656,9 @@ mod tests {
Some(first_date_time)
);
date_time += chrono::Duration::milliseconds(100);
clock.advance(chrono::Duration::milliseconds(100));
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
@ -661,10 +673,10 @@ mod tests {
Some(first_date_time)
);
date_time += chrono::Duration::milliseconds(100);
clock.advance(chrono::Duration::milliseconds(100));
// Adding a 4th event should cause a flush
let event = telemetry.report_app_event_with_date_time(operation.clone(), date_time);
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
@ -680,22 +692,24 @@ mod tests {
#[gpui::test]
async fn test_connection_timeout(executor: BackgroundExecutor, cx: &mut TestAppContext) {
init_test(cx);
let clock = Arc::new(FakeSystemClock::new(
Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(),
));
let http = FakeHttpClient::with_200_response();
let installation_id = Some("installation_id".to_string());
let session_id = "session_id".to_string();
cx.update(|cx| {
let telemetry = Telemetry::new(http, cx);
let telemetry = Telemetry::new(clock.clone(), http, cx);
telemetry.state.lock().max_queue_size = 4;
telemetry.start(installation_id, session_id, cx);
assert!(is_empty_state(&telemetry));
let first_date_time = Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap();
let first_date_time = clock.utc_now();
let operation = "test".to_string();
let event =
telemetry.report_app_event_with_date_time(operation.clone(), first_date_time);
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {