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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ serde_json.workspace = true
settings.workspace = true
smol.workspace = true
strum.workspace = true
telemetry_events.workspace = true
telemetry.workspace = true
theme.workspace = true
thiserror.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 std::env;
use std::sync::Arc;
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use telemetry::{AssistantEvent, AssistantKind, AssistantPhase};
use util::ResultExt;
use crate::provider::anthropic::PROVIDER_ID as ANTHROPIC_PROVIDER_ID;

View File

@@ -55,7 +55,7 @@ settings.workspace = true
shellexpand.workspace = true
smol.workspace = true
sysinfo.workspace = true
telemetry_events.workspace = true
telemetry.workspace = true
util.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::io::AsyncReadExt;
use ::telemetry::LocationData;
use smol::Async;
use smol::{net::unix::UnixListener, stream::StreamExt as _};
use std::ffi::OsStr;
@@ -40,7 +41,6 @@ use std::{
path::{Path, PathBuf},
sync::Arc,
};
use telemetry_events::LocationData;
use util::ResultExt;
fn init_logging_proxy() {
@@ -147,7 +147,7 @@ fn init_panic_hook() {
(&backtrace).join("\n")
);
let panic_data = telemetry_events::Panic {
let panic_data = ::telemetry::Panic {
thread: thread_name.into(),
payload: payload.clone(),
location_data: info.location().map(|location| LocationData {

View File

@@ -1,5 +1,5 @@
[package]
name = "telemetry_events"
name = "telemetry"
version = "0.1.0"
edition = "2021"
publish = false
@@ -9,8 +9,10 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/telemetry_events.rs"
path = "src/telemetry.rs"
[dependencies]
semantic_version.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.
use futures::channel::mpsc;
use semantic_version::SemanticVersion;
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)]
pub struct EventRequestBody {
@@ -32,14 +39,14 @@ impl EventRequestBody {
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct EventWrapper {
pub signed_in: bool,
/// Duration between this event's timestamp and the timestamp of the first event in the current batch
pub milliseconds_since_first_event: i64,
/// The event itself
#[serde(flatten)]
pub event: Event,
pub event: EventBody,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -90,7 +97,8 @@ impl Display for AssistantPhase {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum Event {
pub enum EventBody {
Event(Event),
Editor(EditorEvent),
InlineCompletion(InlineCompletionEvent),
Call(CallEvent),
@@ -265,3 +273,60 @@ pub struct Panic {
pub struct PanicRequest {
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
task.workspace = true
tasks_ui.workspace = true
telemetry_events.workspace = true
telemetry.workspace = true
terminal_view.workspace = true
theme.workspace = true
theme_selector.workspace = true

View File

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