Add telemetry::event! (#22146)

CC @JosephTLyons

Release Notes:

- N/A
This commit is contained in:
Conrad Irwin 2024-12-17 11:39:18 -07:00 committed by GitHub
parent b17f2089a2
commit 7425d242bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 179 additions and 140 deletions

16
Cargo.lock generated
View file

@ -2547,6 +2547,7 @@ dependencies = [
"settings", "settings",
"sha2", "sha2",
"smol", "smol",
"telemetry",
"telemetry_events", "telemetry_events",
"text", "text",
"thiserror 1.0.69", "thiserror 1.0.69",
@ -2841,6 +2842,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"settings", "settings",
"telemetry",
"theme", "theme",
"ui", "ui",
"util", "util",
@ -3938,6 +3940,7 @@ dependencies = [
"snippet", "snippet",
"sum_tree", "sum_tree",
"task", "task",
"telemetry",
"tempfile", "tempfile",
"text", "text",
"theme", "theme",
@ -4373,6 +4376,7 @@ dependencies = [
"serde_json_lenient", "serde_json_lenient",
"settings", "settings",
"task", "task",
"telemetry",
"tempfile", "tempfile",
"theme", "theme",
"theme_extension", "theme_extension",
@ -10399,6 +10403,7 @@ dependencies = [
"serde_json", "serde_json",
"settings", "settings",
"smol", "smol",
"telemetry",
"terminal", "terminal",
"terminal_view", "terminal_view",
"theme", "theme",
@ -12663,12 +12668,23 @@ dependencies = [
"zed_actions", "zed_actions",
] ]
[[package]]
name = "telemetry"
version = "0.1.0"
dependencies = [
"futures 0.3.31",
"serde",
"serde_json",
"telemetry_events",
]
[[package]] [[package]]
name = "telemetry_events" name = "telemetry_events"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"semantic_version", "semantic_version",
"serde", "serde",
"serde_json",
] ]
[[package]] [[package]]

View file

@ -117,6 +117,7 @@ members = [
"crates/tab_switcher", "crates/tab_switcher",
"crates/task", "crates/task",
"crates/tasks_ui", "crates/tasks_ui",
"crates/telemetry",
"crates/telemetry_events", "crates/telemetry_events",
"crates/terminal", "crates/terminal",
"crates/terminal_view", "crates/terminal_view",
@ -305,6 +306,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 = { path = "crates/telemetry" }
telemetry_events = { path = "crates/telemetry_events" } telemetry_events = { path = "crates/telemetry_events" }
terminal = { path = "crates/terminal" } terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" } terminal_view = { path = "crates/terminal_view" }

View file

@ -51,6 +51,7 @@ tokio-socks = { version = "0.5.2", default-features = false, features = ["future
url.workspace = true url.workspace = true
util.workspace = true util.workspace = true
worktree.workspace = true worktree.workspace = true
telemetry.workspace = true
[dev-dependencies] [dev-dependencies]
clock = { workspace = true, features = ["test-support"] } clock = { workspace = true, features = ["test-support"] }

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;
@ -17,9 +18,8 @@ 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_events::{
ActionEvent, AppEvent, AssistantEvent, CallEvent, EditEvent, EditorEvent, Event, AppEvent, AssistantEvent, CallEvent, EditEvent, Event, EventRequestBody, EventWrapper,
EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, InlineCompletionRating, InlineCompletionEvent, InlineCompletionRating, InlineCompletionRatingEvent, SettingEvent,
InlineCompletionRatingEvent, ReplEvent, SettingEvent,
}; };
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
use worktree::{UpdatedEntriesSet, WorktreeId}; use worktree::{UpdatedEntriesSet, WorktreeId};
@ -245,7 +245,6 @@ impl Telemetry {
}) })
.detach(); .detach();
// TODO: Replace all hardware stuff with nested SystemSpecs json
let this = Arc::new(Self { let this = Arc::new(Self {
clock, clock,
http_client: client, http_client: client,
@ -253,6 +252,21 @@ impl Telemetry {
state, state,
}); });
let (tx, mut rx) = mpsc::unbounded();
::telemetry::init(tx);
cx.background_executor()
.spawn({
let this = Arc::downgrade(&this);
async move {
while let Some(event) = rx.next().await {
let Some(state) = this.upgrade() else { break };
state.report_event(Event::Flexible(event))
}
}
})
.detach();
// We should only ever have one instance of Telemetry, leak the subscription to keep it alive // We should only ever have one instance of Telemetry, leak the subscription to keep it alive
// rather than store in TelemetryState, complicating spawn as subscriptions are not Send // rather than store in TelemetryState, complicating spawn as subscriptions are not Send
std::mem::forget(cx.on_app_quit({ std::mem::forget(cx.on_app_quit({
@ -320,27 +334,6 @@ impl Telemetry {
drop(state); drop(state);
} }
pub fn report_editor_event(
self: &Arc<Self>,
file_extension: Option<String>,
vim_mode: bool,
operation: &'static str,
copilot_enabled: bool,
copilot_enabled_for_language: bool,
is_via_ssh: bool,
) {
let event = Event::Editor(EditorEvent {
file_extension,
vim_mode,
operation: operation.into(),
copilot_enabled,
copilot_enabled_for_language,
is_via_ssh,
});
self.report_event(event)
}
pub fn report_inline_completion_event( pub fn report_inline_completion_event(
self: &Arc<Self>, self: &Arc<Self>,
provider: String, provider: String,
@ -410,13 +403,6 @@ impl Telemetry {
self.report_event(event) self.report_event(event)
} }
pub fn report_extension_event(self: &Arc<Self>, extension_id: Arc<str>, version: Arc<str>) {
self.report_event(Event::Extension(ExtensionEvent {
extension_id,
version,
}))
}
pub fn log_edit_event(self: &Arc<Self>, environment: &'static str, is_via_ssh: bool) { pub fn log_edit_event(self: &Arc<Self>, environment: &'static str, is_via_ssh: bool) {
let mut state = self.state.lock(); let mut state = self.state.lock();
let period_data = state.event_coalescer.log_event(environment); let period_data = state.event_coalescer.log_event(environment);
@ -436,15 +422,6 @@ impl Telemetry {
} }
} }
pub fn report_action_event(self: &Arc<Self>, source: &'static str, action: String) {
let event = Event::Action(ActionEvent {
source: source.to_string(),
action,
});
self.report_event(event)
}
pub fn report_discovered_project_events( pub fn report_discovered_project_events(
self: &Arc<Self>, self: &Arc<Self>,
worktree_id: WorktreeId, worktree_id: WorktreeId,
@ -491,21 +468,6 @@ impl Telemetry {
} }
} }
pub fn report_repl_event(
self: &Arc<Self>,
kernel_language: String,
kernel_status: String,
repl_session_id: String,
) {
let event = Event::Repl(ReplEvent {
kernel_language,
kernel_status,
repl_session_id,
});
self.report_event(event)
}
fn report_event(self: &Arc<Self>, event: Event) { fn report_event(self: &Arc<Self>, event: Event) {
let mut state = self.state.lock(); let mut state = self.state.lock();

View file

@ -610,6 +610,10 @@ fn for_snowflake(
"Kernel Status Changed".to_string(), "Kernel Status Changed".to_string(),
serde_json::to_value(e).unwrap(), serde_json::to_value(e).unwrap(),
), ),
Event::Flexible(e) => (
e.event_type.clone(),
serde_json::to_value(&e.event_properties).unwrap(),
),
}; };
if let serde_json::Value::Object(ref mut map) = event_properties { if let serde_json::Value::Object(ref mut map) = event_properties {

View file

@ -25,6 +25,7 @@ settings.workspace = true
theme.workspace = true theme.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true util.workspace = true
telemetry.workspace = true
workspace.workspace = true workspace.workspace = true
zed_actions.workspace = true zed_actions.workspace = true

View file

@ -4,7 +4,7 @@ use std::{
time::Duration, time::Duration,
}; };
use client::{parse_zed_link, telemetry::Telemetry}; use client::parse_zed_link;
use collections::HashMap; use collections::HashMap;
use command_palette_hooks::{ use command_palette_hooks::{
CommandInterceptResult, CommandPaletteFilter, CommandPaletteInterceptor, CommandInterceptResult, CommandPaletteFilter, CommandPaletteInterceptor,
@ -63,18 +63,12 @@ impl CommandPalette {
let Some(previous_focus_handle) = cx.focused() else { let Some(previous_focus_handle) = cx.focused() else {
return; return;
}; };
let telemetry = workspace.client().telemetry().clone();
workspace.toggle_modal(cx, move |cx| { workspace.toggle_modal(cx, move |cx| {
CommandPalette::new(previous_focus_handle, telemetry, query, cx) CommandPalette::new(previous_focus_handle, query, cx)
}); });
} }
fn new( fn new(previous_focus_handle: FocusHandle, query: &str, cx: &mut ViewContext<Self>) -> Self {
previous_focus_handle: FocusHandle,
telemetry: Arc<Telemetry>,
query: &str,
cx: &mut ViewContext<Self>,
) -> Self {
let filter = CommandPaletteFilter::try_global(cx); let filter = CommandPaletteFilter::try_global(cx);
let commands = cx let commands = cx
@ -92,12 +86,8 @@ impl CommandPalette {
}) })
.collect(); .collect();
let delegate = CommandPaletteDelegate::new( let delegate =
cx.view().downgrade(), CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle);
commands,
telemetry,
previous_focus_handle,
);
let picker = cx.new_view(|cx| { let picker = cx.new_view(|cx| {
let picker = Picker::uniform_list(delegate, cx); let picker = Picker::uniform_list(delegate, cx);
@ -133,7 +123,6 @@ pub struct CommandPaletteDelegate {
commands: Vec<Command>, commands: Vec<Command>,
matches: Vec<StringMatch>, matches: Vec<StringMatch>,
selected_ix: usize, selected_ix: usize,
telemetry: Arc<Telemetry>,
previous_focus_handle: FocusHandle, previous_focus_handle: FocusHandle,
updating_matches: Option<( updating_matches: Option<(
Task<()>, Task<()>,
@ -167,7 +156,6 @@ impl CommandPaletteDelegate {
fn new( fn new(
command_palette: WeakView<CommandPalette>, command_palette: WeakView<CommandPalette>,
commands: Vec<Command>, commands: Vec<Command>,
telemetry: Arc<Telemetry>,
previous_focus_handle: FocusHandle, previous_focus_handle: FocusHandle,
) -> Self { ) -> Self {
Self { Self {
@ -176,7 +164,6 @@ impl CommandPaletteDelegate {
matches: vec![], matches: vec![],
commands, commands,
selected_ix: 0, selected_ix: 0,
telemetry,
previous_focus_handle, previous_focus_handle,
updating_matches: None, updating_matches: None,
} }
@ -367,9 +354,11 @@ impl PickerDelegate for CommandPaletteDelegate {
let action_ix = self.matches[self.selected_ix].candidate_id; let action_ix = self.matches[self.selected_ix].candidate_id;
let command = self.commands.swap_remove(action_ix); let command = self.commands.swap_remove(action_ix);
self.telemetry telemetry::event!(
.report_action_event("command palette", command.name.clone()); "Action Invoked",
source = "command palette",
action = command.name
);
self.matches.clear(); self.matches.clear();
self.commands.clear(); self.commands.clear();
HitCounts::update_global(cx, |hit_counts, _cx| { HitCounts::update_global(cx, |hit_counts, _cx| {

View file

@ -72,6 +72,7 @@ smol.workspace = true
snippet.workspace = true snippet.workspace = true
sum_tree.workspace = true sum_tree.workspace = true
task.workspace = true task.workspace = true
telemetry.workspace = true
text.workspace = true text.workspace = true
time.workspace = true time.workspace = true
time_format.workspace = true time_format.workspace = true

View file

@ -1370,7 +1370,7 @@ impl Editor {
} }
} }
this.report_editor_event("open", None, cx); this.report_editor_event("Editor Opened", None, cx);
this this
} }
@ -12568,7 +12568,7 @@ impl Editor {
fn report_editor_event( fn report_editor_event(
&self, &self,
operation: &'static str, event_type: &'static str,
file_extension: Option<String>, file_extension: Option<String>,
cx: &AppContext, cx: &AppContext,
) { ) {
@ -12605,15 +12605,14 @@ impl Editor {
.show_inline_completions; .show_inline_completions;
let project = project.read(cx); let project = project.read(cx);
let telemetry = project.client().telemetry().clone(); telemetry::event!(
telemetry.report_editor_event( event_type,
file_extension, file_extension,
vim_mode, vim_mode,
operation,
copilot_enabled, copilot_enabled,
copilot_enabled_for_language, copilot_enabled_for_language,
project.is_via_ssh(), is_via_ssh = project.is_via_ssh(),
) );
} }
/// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines, /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,

View file

@ -733,7 +733,7 @@ impl Item for Editor {
project: Model<Project>, project: Model<Project>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
self.report_editor_event("save", None, cx); self.report_editor_event("Editor Saved", None, cx);
let buffers = self.buffer().clone().read(cx).all_buffers(); let buffers = self.buffer().clone().read(cx).all_buffers();
let buffers = buffers let buffers = buffers
.into_iter() .into_iter()
@ -805,7 +805,7 @@ impl Item for Editor {
.path .path
.extension() .extension()
.map(|a| a.to_string_lossy().to_string()); .map(|a| a.to_string_lossy().to_string());
self.report_editor_event("save", file_extension, cx); self.report_editor_event("Editor Saved", file_extension, cx);
project.update(cx, |project, cx| project.save_buffer_as(buffer, path, cx)) project.update(cx, |project, cx| project.save_buffer_as(buffer, path, cx))
} }

View file

@ -43,6 +43,7 @@ serde_json.workspace = true
serde_json_lenient.workspace = true serde_json_lenient.workspace = true
settings.workspace = true settings.workspace = true
task.workspace = true task.workspace = true
telemetry.workspace = true
tempfile.workspace = true tempfile.workspace = true
toml.workspace = true toml.workspace = true
url.workspace = true url.workspace = true

View file

@ -1001,14 +1001,13 @@ impl ExtensionStore {
extensions_to_unload.len() - reload_count extensions_to_unload.len() - reload_count
); );
if let Some(telemetry) = &self.telemetry { for extension_id in &extensions_to_load {
for extension_id in &extensions_to_load { if let Some(extension) = new_index.extensions.get(extension_id) {
if let Some(extension) = new_index.extensions.get(extension_id) { telemetry::event!(
telemetry.report_extension_event( "Extension Loaded",
extension_id.clone(), extension_id,
extension.manifest.version.clone(), version = extension.manifest.version
); );
}
} }
} }

View file

@ -43,6 +43,7 @@ serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
settings.workspace = true settings.workspace = true
smol.workspace = true smol.workspace = true
telemetry.workspace = true
terminal.workspace = true terminal.workspace = true
terminal_view.workspace = true terminal_view.workspace = true
theme.workspace = true theme.workspace = true

View file

@ -24,16 +24,15 @@ pub use crate::repl_sessions_ui::{
}; };
use crate::repl_store::ReplStore; use crate::repl_store::ReplStore;
pub use crate::session::Session; pub use crate::session::Session;
use client::telemetry::Telemetry;
pub const KERNEL_DOCS_URL: &str = "https://zed.dev/docs/repl#changing-kernels"; pub const KERNEL_DOCS_URL: &str = "https://zed.dev/docs/repl#changing-kernels";
pub fn init(fs: Arc<dyn Fs>, telemetry: Arc<Telemetry>, cx: &mut AppContext) { pub fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
set_dispatcher(zed_dispatcher(cx)); set_dispatcher(zed_dispatcher(cx));
JupyterSettings::register(cx); JupyterSettings::register(cx);
::editor::init_settings(cx); ::editor::init_settings(cx);
repl_sessions_ui::init(cx); repl_sessions_ui::init(cx);
ReplStore::init(fs, telemetry, cx); ReplStore::init(fs, cx);
} }
fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher { fn zed_dispatcher(cx: &mut AppContext) -> impl Dispatcher {

View file

@ -33,7 +33,6 @@ pub fn assign_kernelspec(
}); });
let fs = store.read(cx).fs().clone(); let fs = store.read(cx).fs().clone();
let telemetry = store.read(cx).telemetry().clone();
if let Some(session) = store.read(cx).get_session(weak_editor.entity_id()).cloned() { if let Some(session) = store.read(cx).get_session(weak_editor.entity_id()).cloned() {
// Drop previous session, start new one // Drop previous session, start new one
@ -44,8 +43,7 @@ pub fn assign_kernelspec(
}); });
} }
let session = cx let session = cx.new_view(|cx| Session::new(weak_editor.clone(), fs, kernel_specification, cx));
.new_view(|cx| Session::new(weak_editor.clone(), fs, telemetry, kernel_specification, cx));
weak_editor weak_editor
.update(cx, |_editor, cx| { .update(cx, |_editor, cx| {
@ -105,15 +103,13 @@ pub fn run(editor: WeakView<Editor>, move_down: bool, cx: &mut WindowContext) ->
.ok_or_else(|| anyhow::anyhow!("No kernel found for language: {}", language.name()))?; .ok_or_else(|| anyhow::anyhow!("No kernel found for language: {}", language.name()))?;
let fs = store.read(cx).fs().clone(); let fs = store.read(cx).fs().clone();
let telemetry = store.read(cx).telemetry().clone();
let session = if let Some(session) = store.read(cx).get_session(editor.entity_id()).cloned() let session = if let Some(session) = store.read(cx).get_session(editor.entity_id()).cloned()
{ {
session session
} else { } else {
let weak_editor = editor.downgrade(); let weak_editor = editor.downgrade();
let session = cx let session = cx.new_view(|cx| Session::new(weak_editor, fs, kernel_specification, cx));
.new_view(|cx| Session::new(weak_editor, fs, telemetry, kernel_specification, cx));
editor.update(cx, |_editor, cx| { editor.update(cx, |_editor, cx| {
cx.notify(); cx.notify();

View file

@ -1,7 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use client::telemetry::Telemetry;
use collections::HashMap; use collections::HashMap;
use command_palette_hooks::CommandPaletteFilter; use command_palette_hooks::CommandPaletteFilter;
use gpui::{ use gpui::{
@ -28,15 +27,14 @@ pub struct ReplStore {
kernel_specifications: Vec<KernelSpecification>, kernel_specifications: Vec<KernelSpecification>,
selected_kernel_for_worktree: HashMap<WorktreeId, KernelSpecification>, selected_kernel_for_worktree: HashMap<WorktreeId, KernelSpecification>,
kernel_specifications_for_worktree: HashMap<WorktreeId, Vec<KernelSpecification>>, kernel_specifications_for_worktree: HashMap<WorktreeId, Vec<KernelSpecification>>,
telemetry: Arc<Telemetry>,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
impl ReplStore { impl ReplStore {
const NAMESPACE: &'static str = "repl"; const NAMESPACE: &'static str = "repl";
pub(crate) fn init(fs: Arc<dyn Fs>, telemetry: Arc<Telemetry>, cx: &mut AppContext) { pub(crate) fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
let store = cx.new_model(move |cx| Self::new(fs, telemetry, cx)); let store = cx.new_model(move |cx| Self::new(fs, cx));
store store
.update(cx, |store, cx| store.refresh_kernelspecs(cx)) .update(cx, |store, cx| store.refresh_kernelspecs(cx))
@ -49,14 +47,13 @@ impl ReplStore {
cx.global::<GlobalReplStore>().0.clone() cx.global::<GlobalReplStore>().0.clone()
} }
pub fn new(fs: Arc<dyn Fs>, telemetry: Arc<Telemetry>, cx: &mut ModelContext<Self>) -> Self { pub fn new(fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
let subscriptions = vec![cx.observe_global::<SettingsStore>(move |this, cx| { let subscriptions = vec![cx.observe_global::<SettingsStore>(move |this, cx| {
this.set_enabled(JupyterSettings::enabled(cx), cx); this.set_enabled(JupyterSettings::enabled(cx), cx);
})]; })];
let this = Self { let this = Self {
fs, fs,
telemetry,
enabled: JupyterSettings::enabled(cx), enabled: JupyterSettings::enabled(cx),
sessions: HashMap::default(), sessions: HashMap::default(),
kernel_specifications: Vec::new(), kernel_specifications: Vec::new(),
@ -72,10 +69,6 @@ impl ReplStore {
&self.fs &self.fs
} }
pub fn telemetry(&self) -> &Arc<Telemetry> {
&self.telemetry
}
pub fn is_enabled(&self) -> bool { pub fn is_enabled(&self) -> bool {
self.enabled self.enabled
} }

View file

@ -6,7 +6,6 @@ use crate::{
outputs::{ExecutionStatus, ExecutionView}, outputs::{ExecutionStatus, ExecutionView},
KernelStatus, KernelStatus,
}; };
use client::telemetry::Telemetry;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use editor::{ use editor::{
display_map::{ display_map::{
@ -37,7 +36,6 @@ pub struct Session {
pub kernel: Kernel, pub kernel: Kernel,
blocks: HashMap<String, EditorBlock>, blocks: HashMap<String, EditorBlock>,
pub kernel_specification: KernelSpecification, pub kernel_specification: KernelSpecification,
telemetry: Arc<Telemetry>,
_buffer_subscription: Subscription, _buffer_subscription: Subscription,
} }
@ -194,7 +192,6 @@ impl Session {
pub fn new( pub fn new(
editor: WeakView<Editor>, editor: WeakView<Editor>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
telemetry: Arc<Telemetry>,
kernel_specification: KernelSpecification, kernel_specification: KernelSpecification,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
@ -221,7 +218,6 @@ impl Session {
blocks: HashMap::default(), blocks: HashMap::default(),
kernel_specification, kernel_specification,
_buffer_subscription: subscription, _buffer_subscription: subscription,
telemetry,
}; };
session.start_kernel(cx); session.start_kernel(cx);
@ -237,10 +233,11 @@ impl Session {
.and_then(|editor| editor.read(cx).working_directory(cx)) .and_then(|editor| editor.read(cx).working_directory(cx))
.unwrap_or_else(temp_dir); .unwrap_or_else(temp_dir);
self.telemetry.report_repl_event( telemetry::event!(
kernel_language.into(), "Kernel Status Changed",
KernelStatus::Starting.to_string(), kernel_language,
cx.entity_id().to_string(), kernel_status = KernelStatus::Starting.to_string(),
repl_session_id = cx.entity_id().to_string(),
); );
let session_view = cx.view().clone(); let session_view = cx.view().clone();
@ -488,10 +485,11 @@ impl Session {
JupyterMessageContent::Status(status) => { JupyterMessageContent::Status(status) => {
self.kernel.set_execution_state(&status.execution_state); self.kernel.set_execution_state(&status.execution_state);
self.telemetry.report_repl_event( telemetry::event!(
self.kernel_specification.language().into(), "Kernel Status Changed",
KernelStatus::from(&self.kernel).to_string(), kernel_language = self.kernel_specification.language(),
cx.entity_id().to_string(), kernel_status = KernelStatus::from(&self.kernel).to_string(),
repl_session_id = cx.entity_id().to_string(),
); );
cx.notify(); cx.notify();
@ -540,12 +538,13 @@ impl Session {
} }
let kernel_status = KernelStatus::from(&kernel).to_string(); let kernel_status = KernelStatus::from(&kernel).to_string();
let kernel_language = self.kernel_specification.language().into(); let kernel_language = self.kernel_specification.language();
self.telemetry.report_repl_event( telemetry::event!(
"Kernel Status Changed",
kernel_language, kernel_language,
kernel_status, kernel_status,
cx.entity_id().to_string(), repl_session_id = cx.entity_id().to_string(),
); );
self.kernel = kernel; self.kernel = kernel;

View file

@ -0,0 +1,18 @@
[package]
name = "telemetry"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/telemetry.rs"
[dependencies]
serde.workspace = true
serde_json.workspace = true
telemetry_events.workspace = true
futures.workspace = true

View file

@ -0,0 +1 @@
LICENSE-GPL

View file

@ -0,0 +1,57 @@
//! See [Telemetry in Zed](https://zed.dev/docs/telemetry) for additional information.
use futures::channel::mpsc;
pub use serde_json;
use std::sync::OnceLock;
pub use telemetry_events::FlexibleEvent as Event;
/// 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 {
event_type: $name.to_string(),
event_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
};
}
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

@ -14,3 +14,4 @@ path = "src/telemetry_events.rs"
[dependencies] [dependencies]
semantic_version.workspace = true semantic_version.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true

View file

@ -2,7 +2,7 @@
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt::Display, sync::Arc, time::Duration}; use std::{collections::HashMap, fmt::Display, sync::Arc, time::Duration};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct EventRequestBody { pub struct EventRequestBody {
@ -91,6 +91,7 @@ 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 Event {
Flexible(FlexibleEvent),
Editor(EditorEvent), Editor(EditorEvent),
InlineCompletion(InlineCompletionEvent), InlineCompletion(InlineCompletionEvent),
InlineCompletionRating(InlineCompletionRatingEvent), InlineCompletionRating(InlineCompletionRatingEvent),
@ -106,6 +107,12 @@ pub enum Event {
Repl(ReplEvent), Repl(ReplEvent),
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct FlexibleEvent {
pub event_type: String,
pub event_properties: HashMap<String, serde_json::Value>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct EditorEvent { pub struct EditorEvent {
/// The editor operation performed (open, save) /// The editor operation performed (open, save)

View file

@ -413,11 +413,7 @@ fn main() {
cx, cx,
); );
assistant_tools::init(cx); assistant_tools::init(cx);
repl::init( repl::init(app_state.fs.clone(), cx);
app_state.fs.clone(),
app_state.client.telemetry().clone(),
cx,
);
extension_host::init( extension_host::init(
extension_host_proxy, extension_host_proxy,
app_state.fs.clone(), app_state.fs.clone(),

View file

@ -3496,11 +3496,7 @@ mod tests {
); );
let prompt_builder = let prompt_builder =
assistant::init(app_state.fs.clone(), app_state.client.clone(), false, cx); assistant::init(app_state.fs.clone(), app_state.client.clone(), false, cx);
repl::init( repl::init(app_state.fs.clone(), cx);
app_state.fs.clone(),
app_state.client.telemetry().clone(),
cx,
);
repl::notebook::init(cx); repl::notebook::init(cx);
tasks_ui::init(cx); tasks_ui::init(cx);
initialize_workspace(app_state.clone(), prompt_builder, cx); initialize_workspace(app_state.clone(), prompt_builder, cx);