Compare commits

...

7 Commits

Author SHA1 Message Date
Conrad Irwin
fce2b15348 Merge branch 'main' into telemetry-event 2024-11-22 10:24:13 -07:00
Conrad Irwin
bf232e27ca Fix clippy 2024-11-20 11:06:29 -07:00
Conrad Irwin
6a8b788f89 partialeq 2024-11-19 18:51:48 -07:00
Conrad Irwin
96dd781eb0 More 2024-11-19 16:39:20 -07:00
Conrad Irwin
aae47b0631 telemetry::event! 2024-11-19 13:40:21 -07:00
Conrad Irwin
319eec5bc9 TEMP 2024-11-19 13:40:20 -07:00
Conrad Irwin
7c29f7aac6 rename telemetry_events to telemetry 2024-11-19 13:38:36 -07:00
25 changed files with 218 additions and 143 deletions

30
Cargo.lock generated
View File

@@ -434,7 +434,7 @@ dependencies = [
"smallvec", "smallvec",
"smol", "smol",
"strum 0.25.0", "strum 0.25.0",
"telemetry_events", "telemetry",
"terminal", "terminal",
"terminal_view", "terminal_view",
"text", "text",
@@ -2472,7 +2472,7 @@ dependencies = [
"settings", "settings",
"sha2", "sha2",
"smol", "smol",
"telemetry_events", "telemetry",
"text", "text",
"thiserror 1.0.69", "thiserror 1.0.69",
"time", "time",
@@ -2653,7 +2653,7 @@ dependencies = [
"strum 0.25.0", "strum 0.25.0",
"subtle", "subtle",
"supermaven_api", "supermaven_api",
"telemetry_events", "telemetry",
"text", "text",
"theme", "theme",
"thiserror 1.0.69", "thiserror 1.0.69",
@@ -3366,9 +3366,9 @@ dependencies = [
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.2.9" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.87", "syn 2.0.87",
@@ -4233,6 +4233,7 @@ dependencies = [
"settings", "settings",
"smallvec", "smallvec",
"snippet_provider", "snippet_provider",
"telemetry",
"theme", "theme",
"ui", "ui",
"util", "util",
@@ -6557,6 +6558,7 @@ dependencies = [
"serde_json", "serde_json",
"smol", "smol",
"strum 0.25.0", "strum 0.25.0",
"telemetry",
"ui", "ui",
"util", "util",
] ]
@@ -6589,7 +6591,7 @@ dependencies = [
"settings", "settings",
"smol", "smol",
"strum 0.25.0", "strum 0.25.0",
"telemetry_events", "telemetry",
"theme", "theme",
"thiserror 1.0.69", "thiserror 1.0.69",
"tiktoken-rs", "tiktoken-rs",
@@ -6720,9 +6722,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.164" version = "0.2.162"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
[[package]] [[package]]
name = "libdbus-sys" name = "libdbus-sys"
@@ -9882,7 +9884,7 @@ dependencies = [
"shellexpand 2.1.2", "shellexpand 2.1.2",
"smol", "smol",
"sysinfo", "sysinfo",
"telemetry_events", "telemetry",
"toml 0.8.19", "toml 0.8.19",
"util", "util",
"worktree", "worktree",
@@ -10886,9 +10888,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.133" version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [ dependencies = [
"indexmap 2.6.0", "indexmap 2.6.0",
"itoa", "itoa",
@@ -12200,11 +12202,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "telemetry_events" name = "telemetry"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"futures 0.3.31",
"semantic_version", "semantic_version",
"serde", "serde",
"serde_json",
] ]
[[package]] [[package]]
@@ -15554,7 +15558,7 @@ dependencies = [
"tab_switcher", "tab_switcher",
"task", "task",
"tasks_ui", "tasks_ui",
"telemetry_events", "telemetry",
"terminal_view", "terminal_view",
"theme", "theme",
"theme_selector", "theme_selector",

View File

@@ -111,7 +111,7 @@ members = [
"crates/tab_switcher", "crates/tab_switcher",
"crates/task", "crates/task",
"crates/tasks_ui", "crates/tasks_ui",
"crates/telemetry_events", "crates/telemetry",
"crates/terminal", "crates/terminal",
"crates/terminal_view", "crates/terminal_view",
"crates/text", "crates/text",
@@ -287,7 +287,7 @@ supermaven_api = { path = "crates/supermaven_api" }
tab_switcher = { path = "crates/tab_switcher" } tab_switcher = { path = "crates/tab_switcher" }
task = { path = "crates/task" } task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" } tasks_ui = { path = "crates/tasks_ui" }
telemetry_events = { path = "crates/telemetry_events" } telemetry = { path = "crates/telemetry" }
terminal = { path = "crates/terminal" } terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" } terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" } text = { path = "crates/text" }
@@ -354,6 +354,7 @@ derive_more = "0.99.17"
dirs = "4.0" dirs = "4.0"
ec4rs = "1.1" ec4rs = "1.1"
emojis = "0.6.1" emojis = "0.6.1"
erased-serde = "0.4.5"
env_logger = "0.11" env_logger = "0.11"
exec = "0.3.1" exec = "0.3.1"
fancy-regex = "0.14.0" fancy-regex = "0.14.0"
@@ -603,7 +604,7 @@ snippets_ui = { codegen-units = 1 }
sqlez_macros = { codegen-units = 1 } sqlez_macros = { codegen-units = 1 }
story = { codegen-units = 1 } story = { codegen-units = 1 }
supermaven_api = { codegen-units = 1 } supermaven_api = { codegen-units = 1 }
telemetry_events = { codegen-units = 1 } telemetry = { codegen-units = 1 }
theme_selector = { codegen-units = 1 } theme_selector = { codegen-units = 1 }
time_format = { codegen-units = 1 } time_format = { codegen-units = 1 }
ui_input = { codegen-units = 1 } ui_input = { codegen-units = 1 }

View File

@@ -78,7 +78,7 @@ similar.workspace = true
smallvec.workspace = true smallvec.workspace = true
smol.workspace = true smol.workspace = true
strum.workspace = true strum.workspace = true
telemetry_events.workspace = true telemetry.workspace = true
terminal.workspace = true terminal.workspace = true
terminal_view.workspace = true terminal_view.workspace = true
text.workspace = true text.workspace = true

View File

@@ -49,7 +49,7 @@ use std::{
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry::{AssistantEvent, AssistantKind, AssistantPhase};
use text::{BufferSnapshot, ToPoint}; use text::{BufferSnapshot, ToPoint};
use util::{post_inc, ResultExt, TryFutureExt}; use util::{post_inc, ResultExt, TryFutureExt};
use uuid::Uuid; use uuid::Uuid;

View File

@@ -50,7 +50,7 @@ use std::{
task::{self, Poll}, task::{self, Poll},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::terminal_panel::TerminalPanel; use terminal_view::terminal_panel::TerminalPanel;
use text::{OffsetRangeExt, ToPoint as _}; use text::{OffsetRangeExt, ToPoint as _};
use theme::ThemeSettings; use theme::ThemeSettings;

View File

@@ -26,7 +26,7 @@ use std::{
sync::Arc, sync::Arc,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal; use terminal::Terminal;
use terminal_view::TerminalView; use terminal_view::TerminalView;
use theme::ThemeSettings; use theme::ThemeSettings;

View File

@@ -42,7 +42,7 @@ serde_json.workspace = true
settings.workspace = true settings.workspace = true
sha2.workspace = true sha2.workspace = true
smol.workspace = true smol.workspace = true
telemetry_events.workspace = true telemetry.workspace = true
text.workspace = true text.workspace = true
thiserror.workspace = true thiserror.workspace = true
time.workspace = true time.workspace = true

View File

@@ -49,8 +49,8 @@ use thiserror::Error;
use url::Url; use url::Url;
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
pub use ::telemetry::EventBody;
pub use rpc::*; pub use rpc::*;
pub use telemetry_events::Event;
pub use user::*; pub use user::*;
static ZED_SERVER_URL: LazyLock<Option<String>> = static ZED_SERVER_URL: LazyLock<Option<String>> =

View File

@@ -4,7 +4,8 @@ use crate::{ChannelId, TelemetrySettings};
use anyhow::Result; use anyhow::Result;
use clock::SystemClock; use clock::SystemClock;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use futures::Future; use futures::channel::mpsc;
use futures::{Future, StreamExt};
use gpui::{AppContext, BackgroundExecutor, Task}; use gpui::{AppContext, BackgroundExecutor, Task};
use http_client::{self, AsyncBody, HttpClient, HttpClientWithUrl, Method, Request}; use http_client::{self, AsyncBody, HttpClient, HttpClientWithUrl, Method, Request};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
@@ -16,8 +17,8 @@ use std::fs::File;
use std::io::Write; use std::io::Write;
use std::time::Instant; use std::time::Instant;
use std::{env, mem, path::PathBuf, sync::Arc, time::Duration}; use std::{env, mem, path::PathBuf, sync::Arc, time::Duration};
use telemetry_events::{ use telemetry::{
ActionEvent, AppEvent, AssistantEvent, CallEvent, EditEvent, EditorEvent, Event, ActionEvent, AppEvent, AssistantEvent, CallEvent, EditEvent, EditorEvent, EventBody,
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, ReplEvent, SettingEvent, EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, ReplEvent, SettingEvent,
}; };
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
@@ -287,12 +288,29 @@ impl Telemetry {
session_id: String, session_id: String,
cx: &AppContext, cx: &AppContext,
) { ) {
let (tx, mut rx) = mpsc::unbounded();
let mut state = self.state.lock(); let mut state = self.state.lock();
state.system_id = system_id.map(|id| id.into()); state.system_id = system_id.map(|id| id.into());
state.installation_id = installation_id.map(|id| id.into()); state.installation_id = installation_id.map(|id| id.into());
state.session_id = Some(session_id); state.session_id = Some(session_id);
state.app_version = release_channel::AppVersion::global(cx).to_string(); state.app_version = release_channel::AppVersion::global(cx).to_string();
state.os_name = os_name(); state.os_name = os_name();
drop(state);
let this = Arc::downgrade(&self);
::telemetry::init(tx);
cx.background_executor()
.spawn(async move {
while let Some(event) = rx.next().await {
let Some(this) = this.upgrade() else {
break;
};
this.report_event(EventBody::Event(event));
}
})
.detach();
} }
pub fn metrics_enabled(self: &Arc<Self>) -> bool { pub fn metrics_enabled(self: &Arc<Self>) -> bool {
@@ -328,7 +346,7 @@ impl Telemetry {
copilot_enabled_for_language: bool, copilot_enabled_for_language: bool,
is_via_ssh: bool, is_via_ssh: bool,
) { ) {
let event = Event::Editor(EditorEvent { let event = EventBody::Editor(EditorEvent {
file_extension, file_extension,
vim_mode, vim_mode,
operation: operation.into(), operation: operation.into(),
@@ -346,7 +364,7 @@ impl Telemetry {
suggestion_accepted: bool, suggestion_accepted: bool,
file_extension: Option<String>, file_extension: Option<String>,
) { ) {
let event = Event::InlineCompletion(InlineCompletionEvent { let event = EventBody::InlineCompletion(InlineCompletionEvent {
provider, provider,
suggestion_accepted, suggestion_accepted,
file_extension, file_extension,
@@ -356,7 +374,7 @@ impl Telemetry {
} }
pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) { pub fn report_assistant_event(self: &Arc<Self>, event: AssistantEvent) {
self.report_event(Event::Assistant(event)); self.report_event(EventBody::Assistant(event));
} }
pub fn report_call_event( pub fn report_call_event(
@@ -365,7 +383,7 @@ impl Telemetry {
room_id: Option<u64>, room_id: Option<u64>,
channel_id: Option<ChannelId>, channel_id: Option<ChannelId>,
) { ) {
let event = Event::Call(CallEvent { let event = EventBody::Call(CallEvent {
operation: operation.to_string(), operation: operation.to_string(),
room_id, room_id,
channel_id: channel_id.map(|cid| cid.0), channel_id: channel_id.map(|cid| cid.0),
@@ -374,8 +392,8 @@ impl Telemetry {
self.report_event(event) self.report_event(event)
} }
pub fn report_app_event(self: &Arc<Self>, operation: String) -> Event { pub fn report_app_event(self: &Arc<Self>, operation: String) -> EventBody {
let event = Event::App(AppEvent { operation }); let event = EventBody::App(AppEvent { operation });
self.report_event(event.clone()); self.report_event(event.clone());
@@ -383,7 +401,7 @@ impl Telemetry {
} }
pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) { pub fn report_setting_event(self: &Arc<Self>, setting: &'static str, value: String) {
let event = Event::Setting(SettingEvent { let event = EventBody::Setting(SettingEvent {
setting: setting.to_string(), setting: setting.to_string(),
value, value,
}); });
@@ -392,7 +410,7 @@ impl Telemetry {
} }
pub fn report_extension_event(self: &Arc<Self>, extension_id: Arc<str>, version: Arc<str>) { pub fn report_extension_event(self: &Arc<Self>, extension_id: Arc<str>, version: Arc<str>) {
self.report_event(Event::Extension(ExtensionEvent { self.report_event(EventBody::Extension(ExtensionEvent {
extension_id, extension_id,
version, version,
})) }))
@@ -404,7 +422,7 @@ impl Telemetry {
drop(state); drop(state);
if let Some((start, end, environment)) = period_data { if let Some((start, end, environment)) = period_data {
let event = Event::Edit(EditEvent { let event = EventBody::Edit(EditEvent {
duration: end duration: end
.saturating_duration_since(start) .saturating_duration_since(start)
.min(Duration::from_secs(60 * 60 * 24)) .min(Duration::from_secs(60 * 60 * 24))
@@ -418,7 +436,7 @@ impl Telemetry {
} }
pub fn report_action_event(self: &Arc<Self>, source: &'static str, action: String) { pub fn report_action_event(self: &Arc<Self>, source: &'static str, action: String) {
let event = Event::Action(ActionEvent { let event = EventBody::Action(ActionEvent {
source: source.to_string(), source: source.to_string(),
action, action,
}); });
@@ -478,7 +496,7 @@ impl Telemetry {
kernel_status: String, kernel_status: String,
repl_session_id: String, repl_session_id: String,
) { ) {
let event = Event::Repl(ReplEvent { let event = EventBody::Repl(ReplEvent {
kernel_language, kernel_language,
kernel_status, kernel_status,
repl_session_id, repl_session_id,
@@ -487,7 +505,7 @@ impl Telemetry {
self.report_event(event) self.report_event(event)
} }
fn report_event(self: &Arc<Self>, event: Event) { fn report_event(self: &Arc<Self>, event: EventBody) {
let mut state = self.state.lock(); let mut state = self.state.lock();
if !state.settings.metrics { if !state.settings.metrics {
@@ -669,7 +687,7 @@ mod tests {
let event = telemetry.report_app_event(operation.clone()); let event = telemetry.report_app_event(operation.clone());
assert_eq!( assert_eq!(
event, event,
Event::App(AppEvent { EventBody::App(AppEvent {
operation: operation.clone(), operation: operation.clone(),
}) })
); );
@@ -685,7 +703,7 @@ mod tests {
let event = telemetry.report_app_event(operation.clone()); let event = telemetry.report_app_event(operation.clone());
assert_eq!( assert_eq!(
event, event,
Event::App(AppEvent { EventBody::App(AppEvent {
operation: operation.clone(), operation: operation.clone(),
}) })
); );
@@ -701,7 +719,7 @@ mod tests {
let event = telemetry.report_app_event(operation.clone()); let event = telemetry.report_app_event(operation.clone());
assert_eq!( assert_eq!(
event, event,
Event::App(AppEvent { EventBody::App(AppEvent {
operation: operation.clone(), operation: operation.clone(),
}) })
); );
@@ -718,7 +736,7 @@ mod tests {
let event = telemetry.report_app_event(operation.clone()); let event = telemetry.report_app_event(operation.clone());
assert_eq!( assert_eq!(
event, event,
Event::App(AppEvent { EventBody::App(AppEvent {
operation: operation.clone(), operation: operation.clone(),
}) })
); );
@@ -752,7 +770,7 @@ mod tests {
let event = telemetry.report_app_event(operation.clone()); let event = telemetry.report_app_event(operation.clone());
assert_eq!( assert_eq!(
event, event,
Event::App(AppEvent { EventBody::App(AppEvent {
operation: operation.clone(), operation: operation.clone(),
}) })
); );

View File

@@ -64,7 +64,7 @@ sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "json"
strum.workspace = true strum.workspace = true
subtle.workspace = true subtle.workspace = true
supermaven_api.workspace = true supermaven_api.workspace = true
telemetry_events.workspace = true telemetry.workspace = true
text.workspace = true text.workspace = true
thiserror.workspace = true thiserror.workspace = true
time.workspace = true time.workspace = true

View File

@@ -18,8 +18,8 @@ use serde::{Deserialize, Serialize, Serializer};
use serde_json::json; use serde_json::json;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use telemetry_events::{ use telemetry::{
ActionEvent, AppEvent, AssistantEvent, CallEvent, CpuEvent, EditEvent, EditorEvent, Event, ActionEvent, AppEvent, AssistantEvent, CallEvent, CpuEvent, EditEvent, EditorEvent, EventBody,
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, Panic, EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, Panic,
ReplEvent, SettingEvent, ReplEvent, SettingEvent,
}; };
@@ -240,7 +240,7 @@ pub async fn post_hang(
.ok(); .ok();
} }
let report: telemetry_events::HangReport = serde_json::from_slice(&body).map_err(|err| { let report: telemetry::HangReport = serde_json::from_slice(&body).map_err(|err| {
log::error!("can't parse report json: {err}"); log::error!("can't parse report json: {err}");
Error::Internal(anyhow!(err)) Error::Internal(anyhow!(err))
})?; })?;
@@ -283,7 +283,7 @@ pub async fn post_panic(
))?; ))?;
} }
let report: telemetry_events::PanicRequest = serde_json::from_slice(&body) let report: telemetry::PanicRequest = serde_json::from_slice(&body)
.map_err(|_| Error::http(StatusCode::BAD_REQUEST, "invalid json".into()))?; .map_err(|_| Error::http(StatusCode::BAD_REQUEST, "invalid json".into()))?;
let panic = report.panic; let panic = report.panic;
@@ -400,7 +400,7 @@ pub async fn post_events(
let checksum_matched = checksum == expected; let checksum_matched = checksum == expected;
let request_body: telemetry_events::EventRequestBody = let request_body: telemetry::EventRequestBody =
serde_json::from_slice(&body).map_err(|err| { serde_json::from_slice(&body).map_err(|err| {
log::error!("can't parse event json: {err}"); log::error!("can't parse event json: {err}");
Error::Internal(anyhow!(err)) Error::Internal(anyhow!(err))
@@ -445,7 +445,8 @@ pub async fn post_events(
for wrapper in &request_body.events { for wrapper in &request_body.events {
match &wrapper.event { match &wrapper.event {
Event::Editor(event) => to_upload.editor_events.push(EditorEventRow::from_event( EventBody::Event(_) => continue,
EventBody::Editor(event) => to_upload.editor_events.push(EditorEventRow::from_event(
event.clone(), event.clone(),
wrapper, wrapper,
&request_body, &request_body,
@@ -453,7 +454,7 @@ pub async fn post_events(
country_code.clone(), country_code.clone(),
checksum_matched, checksum_matched,
)), )),
Event::InlineCompletion(event) => { EventBody::InlineCompletion(event) => {
to_upload to_upload
.inline_completion_events .inline_completion_events
.push(InlineCompletionEventRow::from_event( .push(InlineCompletionEventRow::from_event(
@@ -465,14 +466,14 @@ pub async fn post_events(
checksum_matched, checksum_matched,
)) ))
} }
Event::Call(event) => to_upload.call_events.push(CallEventRow::from_event( EventBody::Call(event) => to_upload.call_events.push(CallEventRow::from_event(
event.clone(), event.clone(),
wrapper, wrapper,
&request_body, &request_body,
first_event_at, first_event_at,
checksum_matched, checksum_matched,
)), )),
Event::Assistant(event) => { EventBody::Assistant(event) => {
to_upload to_upload
.assistant_events .assistant_events
.push(AssistantEventRow::from_event( .push(AssistantEventRow::from_event(
@@ -483,36 +484,38 @@ pub async fn post_events(
checksum_matched, checksum_matched,
)) ))
} }
Event::Cpu(_) | Event::Memory(_) => continue, EventBody::Cpu(_) | EventBody::Memory(_) => continue,
Event::App(event) => to_upload.app_events.push(AppEventRow::from_event( EventBody::App(event) => to_upload.app_events.push(AppEventRow::from_event(
event.clone(), event.clone(),
wrapper, wrapper,
&request_body, &request_body,
first_event_at, first_event_at,
checksum_matched, checksum_matched,
)), )),
Event::Setting(event) => to_upload.setting_events.push(SettingEventRow::from_event( EventBody::Setting(event) => {
to_upload.setting_events.push(SettingEventRow::from_event(
event.clone(),
wrapper,
&request_body,
first_event_at,
checksum_matched,
))
}
EventBody::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event(
event.clone(), event.clone(),
wrapper, wrapper,
&request_body, &request_body,
first_event_at, first_event_at,
checksum_matched, checksum_matched,
)), )),
Event::Edit(event) => to_upload.edit_events.push(EditEventRow::from_event( EventBody::Action(event) => to_upload.action_events.push(ActionEventRow::from_event(
event.clone(), event.clone(),
wrapper, wrapper,
&request_body, &request_body,
first_event_at, first_event_at,
checksum_matched, checksum_matched,
)), )),
Event::Action(event) => to_upload.action_events.push(ActionEventRow::from_event( EventBody::Extension(event) => {
event.clone(),
wrapper,
&request_body,
first_event_at,
checksum_matched,
)),
Event::Extension(event) => {
let metadata = app let metadata = app
.db .db
.get_extension_version(&event.extension_id, &event.version) .get_extension_version(&event.extension_id, &event.version)
@@ -528,7 +531,7 @@ pub async fn post_events(
checksum_matched, checksum_matched,
)) ))
} }
Event::Repl(event) => to_upload.repl_events.push(ReplEventRow::from_event( EventBody::Repl(event) => to_upload.repl_events.push(ReplEventRow::from_event(
event.clone(), event.clone(),
wrapper, wrapper,
&request_body, &request_body,
@@ -1387,7 +1390,7 @@ fn for_snowflake(
let timestamp = let timestamp =
first_event_at + Duration::milliseconds(event.milliseconds_since_first_event); first_event_at + Duration::milliseconds(event.milliseconds_since_first_event);
let (event_type, mut event_properties) = match &event.event { let (event_type, mut event_properties) = match &event.event {
Event::Editor(e) => ( EventBody::Editor(e) => (
match e.operation.as_str() { match e.operation.as_str() {
"open" => "Editor Opened".to_string(), "open" => "Editor Opened".to_string(),
"save" => "Editor Saved".to_string(), "save" => "Editor Saved".to_string(),
@@ -1395,7 +1398,8 @@ fn for_snowflake(
}, },
serde_json::to_value(e).unwrap(), serde_json::to_value(e).unwrap(),
), ),
Event::InlineCompletion(e) => ( EventBody::Event(e) => (e.name.clone(), serde_json::to_value(&e.properties).unwrap()),
EventBody::InlineCompletion(e) => (
format!( format!(
"Inline Completion {}", "Inline Completion {}",
if e.suggestion_accepted { if e.suggestion_accepted {
@@ -1406,7 +1410,7 @@ fn for_snowflake(
), ),
serde_json::to_value(e).unwrap(), serde_json::to_value(e).unwrap(),
), ),
Event::Call(e) => { EventBody::Call(e) => {
let event_type = match e.operation.trim() { let event_type = match e.operation.trim() {
"unshare project" => "Project Unshared".to_string(), "unshare project" => "Project Unshared".to_string(),
"open channel notes" => "Channel Notes Opened".to_string(), "open channel notes" => "Channel Notes Opened".to_string(),
@@ -1427,21 +1431,21 @@ fn for_snowflake(
(event_type, serde_json::to_value(e).unwrap()) (event_type, serde_json::to_value(e).unwrap())
} }
Event::Assistant(e) => ( EventBody::Assistant(e) => (
match e.phase { match e.phase {
telemetry_events::AssistantPhase::Response => "Assistant Responded".to_string(), telemetry::AssistantPhase::Response => "Assistant Responded".to_string(),
telemetry_events::AssistantPhase::Invoked => "Assistant Invoked".to_string(), telemetry::AssistantPhase::Invoked => "Assistant Invoked".to_string(),
telemetry_events::AssistantPhase::Accepted => { telemetry::AssistantPhase::Accepted => {
"Assistant Response Accepted".to_string() "Assistant Response Accepted".to_string()
} }
telemetry_events::AssistantPhase::Rejected => { telemetry::AssistantPhase::Rejected => {
"Assistant Response Rejected".to_string() "Assistant Response Rejected".to_string()
} }
}, },
serde_json::to_value(e).unwrap(), serde_json::to_value(e).unwrap(),
), ),
Event::Cpu(_) | Event::Memory(_) => return None, EventBody::Cpu(_) | EventBody::Memory(_) => return None,
Event::App(e) => { EventBody::App(e) => {
let mut properties = json!({}); let mut properties = json!({});
let event_type = match e.operation.trim() { let event_type = match e.operation.trim() {
"extensions: install extension" => "Extension Installed".to_string(), "extensions: install extension" => "Extension Installed".to_string(),
@@ -1522,23 +1526,23 @@ fn for_snowflake(
}; };
(event_type, properties) (event_type, properties)
} }
Event::Setting(e) => ( EventBody::Setting(e) => (
"Settings Changed".to_string(), "Settings Changed".to_string(),
serde_json::to_value(e).unwrap(), serde_json::to_value(e).unwrap(),
), ),
Event::Extension(e) => ( EventBody::Extension(e) => (
"Extension Loaded".to_string(), "Extension Loaded".to_string(),
serde_json::to_value(e).unwrap(), serde_json::to_value(e).unwrap(),
), ),
Event::Edit(e) => ( EventBody::Edit(e) => (
"Editor Edited".to_string(), "Editor Edited".to_string(),
serde_json::to_value(e).unwrap(), serde_json::to_value(e).unwrap(),
), ),
Event::Action(e) => ( EventBody::Action(e) => (
"Action Invoked".to_string(), "Action Invoked".to_string(),
serde_json::to_value(e).unwrap(), serde_json::to_value(e).unwrap(),
), ),
Event::Repl(e) => ( EventBody::Repl(e) => (
"Kernel Status Changed".to_string(), "Kernel Status Changed".to_string(),
serde_json::to_value(e).unwrap(), serde_json::to_value(e).unwrap(),
), ),
@@ -1578,7 +1582,7 @@ fn for_snowflake(
}) })
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug)]
struct SnowflakeRow { struct SnowflakeRow {
pub time: chrono::DateTime<chrono::Utc>, pub time: chrono::DateTime<chrono::Utc>,
pub user_id: Option<String>, pub user_id: Option<String>,

View File

@@ -37,6 +37,7 @@ serde.workspace = true
settings.workspace = true settings.workspace = true
smallvec.workspace = true smallvec.workspace = true
snippet_provider.workspace = true snippet_provider.workspace = true
telemetry.workspace = true
theme.workspace = true theme.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true util.workspace = true

View File

@@ -1,6 +1,3 @@
use std::sync::Arc;
use client::telemetry::Telemetry;
use gpui::{AnyElement, Div, StyleRefinement}; use gpui::{AnyElement, Div, StyleRefinement};
use smallvec::SmallVec; use smallvec::SmallVec;
use ui::{prelude::*, ButtonLike}; use ui::{prelude::*, ButtonLike};
@@ -8,17 +5,15 @@ use ui::{prelude::*, ButtonLike};
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct FeatureUpsell { pub struct FeatureUpsell {
base: Div, base: Div,
telemetry: Arc<Telemetry>,
text: SharedString, text: SharedString,
docs_url: Option<SharedString>, docs_url: Option<SharedString>,
children: SmallVec<[AnyElement; 2]>, children: SmallVec<[AnyElement; 2]>,
} }
impl FeatureUpsell { impl FeatureUpsell {
pub fn new(telemetry: Arc<Telemetry>, text: impl Into<SharedString>) -> Self { pub fn new(text: impl Into<SharedString>) -> Self {
Self { Self {
base: h_flex(), base: h_flex(),
telemetry,
text: text.into(), text: text.into(),
docs_url: None, docs_url: None,
children: SmallVec::new(), children: SmallVec::new(),
@@ -67,12 +62,13 @@ impl RenderOnce for FeatureUpsell {
.child(Icon::new(IconName::ArrowUpRight)), .child(Icon::new(IconName::ArrowUpRight)),
) )
.on_click({ .on_click({
let telemetry = self.telemetry.clone();
let docs_url = docs_url.clone(); let docs_url = docs_url.clone();
move |_event, cx| { move |_event, cx| {
telemetry.report_app_event(format!( telemetry::event!(
"feature upsell: viewed docs ({docs_url})" "Documentation Viewed",
)); source = "Feature Upsell",
url = docs_url
);
cx.open_url(&docs_url) cx.open_url(&docs_url)
} }
}), }),

View File

@@ -975,19 +975,16 @@ impl ExtensionsPage {
let upsells_count = self.upsells.len(); let upsells_count = self.upsells.len();
v_flex().children(self.upsells.iter().enumerate().map(|(ix, feature)| { v_flex().children(self.upsells.iter().enumerate().map(|(ix, feature)| {
let telemetry = self.telemetry.clone();
let upsell = match feature { let upsell = match feature {
Feature::Git => FeatureUpsell::new( Feature::Git => FeatureUpsell::new(
telemetry,
"Zed comes with basic Git support. More Git features are coming in the future.", "Zed comes with basic Git support. More Git features are coming in the future.",
) )
.docs_url("https://zed.dev/docs/git"), .docs_url("https://zed.dev/docs/git"),
Feature::OpenIn => FeatureUpsell::new( Feature::OpenIn => FeatureUpsell::new(
telemetry,
"Zed supports linking to a source line on GitHub and others.", "Zed supports linking to a source line on GitHub and others.",
) )
.docs_url("https://zed.dev/docs/git#git-integrations"), .docs_url("https://zed.dev/docs/git#git-integrations"),
Feature::Vim => FeatureUpsell::new(telemetry, "Vim support is built-in to Zed!") Feature::Vim => FeatureUpsell::new("Vim support is built-in to Zed!")
.docs_url("https://zed.dev/docs/vim") .docs_url("https://zed.dev/docs/vim")
.child(CheckboxWithLabel::new( .child(CheckboxWithLabel::new(
"enable-vim", "enable-vim",
@@ -1007,36 +1004,22 @@ impl ExtensionsPage {
); );
}), }),
)), )),
Feature::LanguageBash => { Feature::LanguageBash => FeatureUpsell::new("Shell support is built-in to Zed!")
FeatureUpsell::new(telemetry, "Shell support is built-in to Zed!") .docs_url("https://zed.dev/docs/languages/bash"),
.docs_url("https://zed.dev/docs/languages/bash") Feature::LanguageC => FeatureUpsell::new("C support is built-in to Zed!")
} .docs_url("https://zed.dev/docs/languages/c"),
Feature::LanguageC => { Feature::LanguageCpp => FeatureUpsell::new("C++ support is built-in to Zed!")
FeatureUpsell::new(telemetry, "C support is built-in to Zed!") .docs_url("https://zed.dev/docs/languages/cpp"),
.docs_url("https://zed.dev/docs/languages/c") Feature::LanguageGo => FeatureUpsell::new("Go support is built-in to Zed!")
} .docs_url("https://zed.dev/docs/languages/go"),
Feature::LanguageCpp => { Feature::LanguagePython => FeatureUpsell::new("Python support is built-in to Zed!")
FeatureUpsell::new(telemetry, "C++ support is built-in to Zed!") .docs_url("https://zed.dev/docs/languages/python"),
.docs_url("https://zed.dev/docs/languages/cpp") Feature::LanguageReact => FeatureUpsell::new("React support is built-in to Zed!")
} .docs_url("https://zed.dev/docs/languages/typescript"),
Feature::LanguageGo => { Feature::LanguageRust => FeatureUpsell::new("Rust support is built-in to Zed!")
FeatureUpsell::new(telemetry, "Go support is built-in to Zed!") .docs_url("https://zed.dev/docs/languages/rust"),
.docs_url("https://zed.dev/docs/languages/go")
}
Feature::LanguagePython => {
FeatureUpsell::new(telemetry, "Python support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/python")
}
Feature::LanguageReact => {
FeatureUpsell::new(telemetry, "React support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/typescript")
}
Feature::LanguageRust => {
FeatureUpsell::new(telemetry, "Rust support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/rust")
}
Feature::LanguageTypescript => { Feature::LanguageTypescript => {
FeatureUpsell::new(telemetry, "Typescript support is built-in to Zed!") FeatureUpsell::new("Typescript support is built-in to Zed!")
.docs_url("https://zed.dev/docs/languages/typescript") .docs_url("https://zed.dev/docs/languages/typescript")
} }
}; };

View File

@@ -35,6 +35,7 @@ serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
smol.workspace = true smol.workspace = true
strum.workspace = true strum.workspace = true
telemetry.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true util.workspace = true

View File

@@ -36,7 +36,7 @@ serde_json.workspace = true
settings.workspace = true settings.workspace = true
smol.workspace = true smol.workspace = true
strum.workspace = true strum.workspace = true
telemetry_events.workspace = true telemetry.workspace = true
theme.workspace = true theme.workspace = true
thiserror.workspace = true thiserror.workspace = true
tiktoken-rs.workspace = true tiktoken-rs.workspace = true

View File

@@ -5,7 +5,7 @@ use gpui::BackgroundExecutor;
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use std::env; use std::env;
use std::sync::Arc; use std::sync::Arc;
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry::{AssistantEvent, AssistantKind, AssistantPhase};
use util::ResultExt; use util::ResultExt;
use crate::provider::anthropic::PROVIDER_ID as ANTHROPIC_PROVIDER_ID; use crate::provider::anthropic::PROVIDER_ID as ANTHROPIC_PROVIDER_ID;

View File

@@ -55,7 +55,7 @@ settings.workspace = true
shellexpand.workspace = true shellexpand.workspace = true
smol.workspace = true smol.workspace = true
sysinfo.workspace = true sysinfo.workspace = true
telemetry_events.workspace = true telemetry.workspace = true
util.workspace = true util.workspace = true
worktree.workspace = true worktree.workspace = true

View File

@@ -28,6 +28,7 @@ use settings::{watch_config_file, Settings, SettingsStore};
use smol::channel::{Receiver, Sender}; use smol::channel::{Receiver, Sender};
use smol::io::AsyncReadExt; use smol::io::AsyncReadExt;
use ::telemetry::LocationData;
use smol::Async; use smol::Async;
use smol::{net::unix::UnixListener, stream::StreamExt as _}; use smol::{net::unix::UnixListener, stream::StreamExt as _};
use std::ffi::OsStr; use std::ffi::OsStr;
@@ -40,7 +41,6 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
}; };
use telemetry_events::LocationData;
use util::ResultExt; use util::ResultExt;
fn init_logging_proxy() { fn init_logging_proxy() {
@@ -147,7 +147,7 @@ fn init_panic_hook() {
(&backtrace).join("\n") (&backtrace).join("\n")
); );
let panic_data = telemetry_events::Panic { let panic_data = ::telemetry::Panic {
thread: thread_name.into(), thread: thread_name.into(),
payload: payload.clone(), payload: payload.clone(),
location_data: info.location().map(|location| LocationData { location_data: info.location().map(|location| LocationData {

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "telemetry_events" name = "telemetry"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = false publish = false
@@ -9,8 +9,10 @@ license = "GPL-3.0-or-later"
workspace = true workspace = true
[lib] [lib]
path = "src/telemetry_events.rs" path = "src/telemetry.rs"
[dependencies] [dependencies]
semantic_version.workspace = true semantic_version.workspace = true
serde.workspace = true serde.workspace = true
futures.workspace = true
serde_json = { workspace = true, features = ["raw_value"] }

View File

@@ -1,8 +1,15 @@
//! See [Telemetry in Zed](https://zed.dev/docs/telemetry) for additional information. //! See [Telemetry in Zed](https://zed.dev/docs/telemetry) for additional information.
use futures::channel::mpsc;
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt::Display, sync::Arc, time::Duration}; pub use serde_json;
use std::{
collections::HashMap,
fmt::Display,
sync::{Arc, OnceLock},
time::Duration,
};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EventRequestBody { pub struct EventRequestBody {
@@ -32,14 +39,14 @@ impl EventRequestBody {
} }
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct EventWrapper { pub struct EventWrapper {
pub signed_in: bool, pub signed_in: bool,
/// Duration between this event's timestamp and the timestamp of the first event in the current batch /// Duration between this event's timestamp and the timestamp of the first event in the current batch
pub milliseconds_since_first_event: i64, pub milliseconds_since_first_event: i64,
/// The event itself /// The event itself
#[serde(flatten)] #[serde(flatten)]
pub event: Event, pub event: EventBody,
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -90,7 +97,8 @@ impl Display for AssistantPhase {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum Event { pub enum EventBody {
Event(Event),
Editor(EditorEvent), Editor(EditorEvent),
InlineCompletion(InlineCompletionEvent), InlineCompletion(InlineCompletionEvent),
Call(CallEvent), Call(CallEvent),
@@ -265,3 +273,60 @@ pub struct Panic {
pub struct PanicRequest { pub struct PanicRequest {
pub panic: Panic, pub panic: Panic,
} }
/// Macro to create telemetry events and send them to the telemetry queue.
///
/// By convention, the name should be "Noun Verbed", e.g. "Keymap Changed"
/// or "Project Diagnostics Opened".
///
/// The properties can be any value that implements serde::Serialize.
///
/// ```
/// telemetry::event!("Keymap Changed", version = "1.0.0");
/// telemetry::event!("Documentation Viewed", url, source = "Extension Upsell");
/// ```
#[macro_export]
macro_rules! event {
($name:expr, $($key:ident $(= $value:expr)?),+ $(,)?) => {{
let event = $crate::Event {
name: $name.to_string(),
properties: std::collections::HashMap::from([
$(
(stringify!($key).to_string(),
$crate::serde_json::value::to_value(&$crate::serialize_property!($key $(= $value)?))
.unwrap_or_else(|_| $crate::serde_json::to_value(&()).unwrap())
),
)+
]),
};
$crate::send_event(event);
}};
}
#[macro_export]
macro_rules! serialize_property {
($key:ident) => {
$key
};
($key:ident = $value:expr) => {
$value
};
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Event {
pub name: String,
pub properties: HashMap<String, serde_json::Value>,
}
pub fn send_event(event: Event) {
if let Some(queue) = TELEMETRY_QUEUE.get() {
queue.unbounded_send(event).ok();
return;
}
}
pub fn init(tx: mpsc::UnboundedSender<Event>) {
TELEMETRY_QUEUE.set(tx).ok();
}
static TELEMETRY_QUEUE: OnceLock<mpsc::UnboundedSender<Event>> = OnceLock::new();

View File

@@ -106,7 +106,7 @@ sysinfo.workspace = true
tab_switcher.workspace = true tab_switcher.workspace = true
task.workspace = true task.workspace = true
tasks_ui.workspace = true tasks_ui.workspace = true
telemetry_events.workspace = true telemetry.workspace = true
terminal_view.workspace = true terminal_view.workspace = true
theme.workspace = true theme.workspace = true
theme_selector.workspace = true theme_selector.workspace = true

View File

@@ -6,6 +6,9 @@ use db::kvp::KEY_VALUE_STORE;
use gpui::{AppContext, SemanticVersion}; use gpui::{AppContext, SemanticVersion};
use http_client::{HttpRequestExt, Method}; use http_client::{HttpRequestExt, Method};
use ::telemetry::LocationData;
use ::telemetry::Panic;
use ::telemetry::PanicRequest;
use http_client::{self, HttpClient, HttpClientWithUrl}; use http_client::{self, HttpClient, HttpClientWithUrl};
use paths::{crashes_dir, crashes_retired_dir}; use paths::{crashes_dir, crashes_retired_dir};
use project::Project; use project::Project;
@@ -19,9 +22,6 @@ use std::{
sync::{atomic::Ordering, Arc}, sync::{atomic::Ordering, Arc},
}; };
use std::{io::Write, panic, sync::atomic::AtomicU32, thread}; use std::{io::Write, panic, sync::atomic::AtomicU32, thread};
use telemetry_events::LocationData;
use telemetry_events::Panic;
use telemetry_events::PanicRequest;
use url::Url; use url::Url;
use util::ResultExt; use util::ResultExt;
@@ -90,7 +90,7 @@ pub fn init_panic_hook(
backtrace.drain(0..=ix); backtrace.drain(0..=ix);
} }
let panic_data = telemetry_events::Panic { let panic_data = ::telemetry::Panic {
thread: thread_name.into(), thread: thread_name.into(),
payload, payload,
location_data: info.location().map(|location| LocationData { location_data: info.location().map(|location| LocationData {
@@ -225,13 +225,13 @@ pub fn monitor_main_thread_hangs(
use parking_lot::Mutex; use parking_lot::Mutex;
use ::telemetry::{BacktraceFrame, HangReport};
use http_client::Method; use http_client::Method;
use std::{ use std::{
ffi::c_int, ffi::c_int,
sync::{mpsc, OnceLock}, sync::{mpsc, OnceLock},
time::Duration, time::Duration,
}; };
use telemetry_events::{BacktraceFrame, HangReport};
use nix::sys::pthread; use nix::sys::pthread;
@@ -490,7 +490,7 @@ async fn upload_previous_panics(
async fn upload_panic( async fn upload_panic(
http: &Arc<HttpClientWithUrl>, http: &Arc<HttpClientWithUrl>,
panic_report_url: &Url, panic_report_url: &Url,
panic: telemetry_events::Panic, panic: ::telemetry::Panic,
most_recent_panic: &mut Option<(i64, String)>, most_recent_panic: &mut Option<(i64, String)>,
) -> Result<bool> { ) -> Result<bool> {
*most_recent_panic = Some((panic.panicked_on, panic.payload.clone())); *most_recent_panic = Some((panic.panicked_on, panic.payload.clone()));

View File

@@ -31,7 +31,7 @@ Telemetry is sent from the application to our servers. Data is proxied through o
Diagnostic events include debug information (stack traces) from crash reports. Reports are sent on the first application launch after the crash occurred. We've built dashboards that allow us to visualize the frequency and severity of issues experienced by users. Having these reports sent automatically allows us to begin implementing fixes without the user needing to file a report in our issue tracker. The plots in the dashboards also give us an informal measurement of the stability of Zed. Diagnostic events include debug information (stack traces) from crash reports. Reports are sent on the first application launch after the crash occurred. We've built dashboards that allow us to visualize the frequency and severity of issues experienced by users. Having these reports sent automatically allows us to begin implementing fixes without the user needing to file a report in our issue tracker. The plots in the dashboards also give us an informal measurement of the stability of Zed.
You can see what data is sent when a panic occurs by inspecting the `Panic` struct in [crates/telemetry_events/src/telemetry_events.rs](https://github.com/zed-industries/zed/blob/main/crates/telemetry_events/src/telemetry_events.rs) in the Zed repo. You can find additional information in the [Debugging Crashes](./development/debugging-crashes.md) documentation. You can see what data is sent when a panic occurs by inspecting the `Panic` struct in [crates/telemetry/src/telemetry.rs](https://github.com/zed-industries/zed/blob/main/crates/telemetry/src/telemetry.rs) in the Zed repo. You can find additional information in the [Debugging Crashes](./development/debugging-crashes.md) documentation.
### Usage Data (Metrics) {#metrics} ### Usage Data (Metrics) {#metrics}