Add telemetry events backend for collab (#8220)

Send telemetry to collab not zed.dev

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
Conrad Irwin 2024-02-23 09:13:28 -07:00 committed by GitHub
parent 6d91224882
commit f19ab464c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1196 additions and 177 deletions

View file

@ -8,7 +8,6 @@ use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use release_channel::ReleaseChannel;
use serde::Serialize;
use settings::{Settings, SettingsStore};
use sha2::{Digest, Sha256};
use std::io::Write;
@ -16,6 +15,10 @@ use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use sysinfo::{
CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
};
use telemetry_events::{
ActionEvent, AppEvent, AssistantEvent, AssistantKind, CallEvent, CopilotEvent, CpuEvent,
EditEvent, EditorEvent, Event, EventRequestBody, EventWrapper, MemoryEvent, SettingEvent,
};
use tempfile::NamedTempFile;
use util::http::{self, HttpClient, Method, ZedHttpClient};
#[cfg(not(debug_assertions))]
@ -35,7 +38,7 @@ struct TelemetryState {
settings: TelemetrySettings,
metrics_id: Option<Arc<str>>, // Per logged-in user
installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
session_id: Option<Arc<str>>, // Per app launch
session_id: Option<String>, // Per app launch
release_channel: Option<&'static str>,
app_metadata: AppMetadata,
architecture: &'static str,
@ -48,93 +51,6 @@ struct TelemetryState {
max_queue_size: usize,
}
#[derive(Serialize, Debug)]
struct EventRequestBody {
installation_id: Option<Arc<str>>,
session_id: Option<Arc<str>>,
is_staff: Option<bool>,
app_version: Option<String>,
os_name: &'static str,
os_version: Option<String>,
architecture: &'static str,
release_channel: Option<&'static str>,
events: Vec<EventWrapper>,
}
#[derive(Serialize, Debug)]
struct EventWrapper {
signed_in: bool,
#[serde(flatten)]
event: Event,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AssistantKind {
Panel,
Inline,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(tag = "type")]
pub enum Event {
Editor {
operation: &'static str,
file_extension: Option<String>,
vim_mode: bool,
copilot_enabled: bool,
copilot_enabled_for_language: bool,
milliseconds_since_first_event: i64,
},
Copilot {
suggestion_id: Option<String>,
suggestion_accepted: bool,
file_extension: Option<String>,
milliseconds_since_first_event: i64,
},
Call {
operation: &'static str,
room_id: Option<u64>,
channel_id: Option<u64>,
milliseconds_since_first_event: i64,
},
Assistant {
conversation_id: Option<String>,
kind: AssistantKind,
model: &'static str,
milliseconds_since_first_event: i64,
},
Cpu {
usage_as_percentage: f32,
core_count: u32,
milliseconds_since_first_event: i64,
},
Memory {
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
milliseconds_since_first_event: i64,
},
App {
operation: String,
milliseconds_since_first_event: i64,
},
Setting {
setting: &'static str,
value: String,
milliseconds_since_first_event: i64,
},
Edit {
duration: i64,
environment: &'static str,
milliseconds_since_first_event: i64,
},
Action {
source: &'static str,
action: String,
milliseconds_since_first_event: i64,
},
}
#[cfg(debug_assertions)]
const MAX_QUEUE_LEN: usize = 5;
@ -146,7 +62,6 @@ const FLUSH_INTERVAL: Duration = Duration::from_secs(1);
#[cfg(not(debug_assertions))]
const FLUSH_INTERVAL: Duration = Duration::from_secs(60 * 5);
static ZED_CLIENT_CHECKSUM_SEED: Lazy<Option<Vec<u8>>> = Lazy::new(|| {
option_env!("ZED_CLIENT_CHECKSUM_SEED")
.map(|s| s.as_bytes().into())
@ -318,15 +233,13 @@ impl Telemetry {
copilot_enabled: bool,
copilot_enabled_for_language: bool,
) {
let event = Event::Editor {
let event = Event::Editor(EditorEvent {
file_extension,
vim_mode,
operation,
operation: operation.into(),
copilot_enabled,
copilot_enabled_for_language,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
@ -337,13 +250,11 @@ impl Telemetry {
suggestion_accepted: bool,
file_extension: Option<String>,
) {
let event = Event::Copilot {
let event = Event::Copilot(CopilotEvent {
suggestion_id,
suggestion_accepted,
file_extension,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
@ -354,13 +265,11 @@ impl Telemetry {
kind: AssistantKind,
model: &'static str,
) {
let event = Event::Assistant {
let event = Event::Assistant(AssistantEvent {
conversation_id,
kind,
model,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
model: model.to_string(),
});
self.report_event(event)
}
@ -371,24 +280,20 @@ impl Telemetry {
room_id: Option<u64>,
channel_id: Option<u64>,
) {
let event = Event::Call {
operation,
let event = Event::Call(CallEvent {
operation: operation.to_string(),
room_id,
channel_id,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
pub fn report_cpu_event(self: &Arc<Self>, usage_as_percentage: f32, core_count: u32) {
let event = Event::Cpu {
let event = Event::Cpu(CpuEvent {
usage_as_percentage,
core_count,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
@ -398,22 +303,16 @@ impl Telemetry {
memory_in_bytes: u64,
virtual_memory_in_bytes: u64,
) {
let event = Event::Memory {
let event = Event::Memory(MemoryEvent {
memory_in_bytes,
virtual_memory_in_bytes,
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) -> Event {
let event = Event::App {
operation,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
let event = Event::App(AppEvent { operation });
self.report_event(event.clone());
@ -421,12 +320,10 @@ impl Telemetry {
}
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
let event = Event::Setting {
setting,
let event = Event::Setting(SettingEvent {
setting: setting.to_string(),
value,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
@ -437,42 +334,24 @@ impl Telemetry {
drop(state);
if let Some((start, end, environment)) = period_data {
let event = Event::Edit {
let event = Event::Edit(EditEvent {
duration: end.timestamp_millis() - start.timestamp_millis(),
environment,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
environment: environment.to_string(),
});
self.report_event(event);
}
}
pub fn report_action_event(self: &Arc<Self>, source: &'static str, action: String) {
let event = Event::Action {
source,
let event = Event::Action(ActionEvent {
source: source.to_string(),
action,
milliseconds_since_first_event: self
.milliseconds_since_first_event(self.clock.utc_now()),
};
});
self.report_event(event)
}
fn milliseconds_since_first_event(self: &Arc<Self>, date_time: DateTime<Utc>) -> i64 {
let mut state = self.state.lock();
match state.first_event_date_time {
Some(first_event_date_time) => {
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
}
None => {
state.first_event_date_time = Some(date_time);
0
}
}
}
fn report_event(self: &Arc<Self>, event: Event) {
let mut state = self.state.lock();
@ -489,8 +368,24 @@ impl Telemetry {
}));
}
let date_time = self.clock.utc_now();
let milliseconds_since_first_event = match state.first_event_date_time {
Some(first_event_date_time) => {
date_time.timestamp_millis() - first_event_date_time.timestamp_millis()
}
None => {
state.first_event_date_time = Some(date_time);
0
}
};
let signed_in = state.metrics_id.is_some();
state.events_queue.push(EventWrapper { signed_in, event });
state.events_queue.push(EventWrapper {
signed_in,
milliseconds_since_first_event,
event,
});
if state.installation_id.is_some() {
if state.events_queue.len() >= state.max_queue_size {
@ -545,21 +440,22 @@ impl Telemetry {
{
let state = this.state.lock();
let request_body = EventRequestBody {
installation_id: state.installation_id.clone(),
installation_id: state.installation_id.as_deref().map(Into::into),
session_id: state.session_id.clone(),
is_staff: state.is_staff.clone(),
app_version: state
.app_metadata
.app_version
.map(|version| version.to_string()),
os_name: state.app_metadata.os_name,
.unwrap_or_default()
.to_string(),
os_name: state.app_metadata.os_name.to_string(),
os_version: state
.app_metadata
.os_version
.map(|version| version.to_string()),
architecture: state.architecture,
architecture: state.architecture.to_string(),
release_channel: state.release_channel,
release_channel: state.release_channel.map(Into::into),
events,
};
json_bytes.clear();
@ -578,7 +474,7 @@ impl Telemetry {
let request = http::Request::builder()
.method(Method::POST)
.uri(&this.http_client.zed_url("/api/events"))
.uri(this.http_client.zed_api_url("/telemetry/events"))
.header("Content-Type", "text/plain")
.header("x-zed-checksum", checksum)
.body(json_bytes.into());
@ -627,10 +523,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 0
}
})
);
assert_eq!(telemetry.state.lock().events_queue.len(), 1);
assert!(telemetry.state.lock().flush_events_task.is_some());
@ -644,10 +539,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 100
}
})
);
assert_eq!(telemetry.state.lock().events_queue.len(), 2);
assert!(telemetry.state.lock().flush_events_task.is_some());
@ -661,10 +555,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 200
}
})
);
assert_eq!(telemetry.state.lock().events_queue.len(), 3);
assert!(telemetry.state.lock().flush_events_task.is_some());
@ -679,10 +572,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 300
}
})
);
assert!(is_empty_state(&telemetry));
@ -712,10 +604,9 @@ mod tests {
let event = telemetry.report_app_event(operation.clone());
assert_eq!(
event,
Event::App {
Event::App(AppEvent {
operation: operation.clone(),
milliseconds_since_first_event: 0
}
})
);
assert_eq!(telemetry.state.lock().events_queue.len(), 1);
assert!(telemetry.state.lock().flush_events_task.is_some());