debugger: Add telemetry for new session experience (#31171)

Piotr Osiewicz and Joseph T. Lyons created

This includes the following data:
- Where we spawned the session from (gutter, scenario list, custom form
filled by the user)
- Which debug adapter was used
- Which dock the debugger is in

Closes #ISSUE

Release Notes:

- debugger: Added telemetry for new session experience that includes
data about:
    - How a session was spawned (gutter, scenario list or custom form)
    - Which debug adapter was used
    - Which dock the debugger is in

---------

Co-authored-by: Joseph T. Lyons <JosephTLyons@gmail.com>

Change summary

Cargo.lock                                  |  2 +
crates/dap/Cargo.toml                       |  1 
crates/dap/src/dap.rs                       | 34 ++++++++++++++++++++++
crates/debugger_ui/src/new_session_modal.rs | 14 ++++++---
crates/editor/Cargo.toml                    |  1 
crates/editor/src/editor.rs                 |  2 +
6 files changed, 48 insertions(+), 6 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4030,6 +4030,7 @@ dependencies = [
  "smallvec",
  "smol",
  "task",
+ "telemetry",
  "util",
  "workspace-hack",
 ]
@@ -4678,6 +4679,7 @@ dependencies = [
  "command_palette_hooks",
  "convert_case 0.8.0",
  "ctor",
+ "dap",
  "db",
  "emojis",
  "env_logger 0.11.8",

crates/dap/Cargo.toml 🔗

@@ -47,6 +47,7 @@ settings.workspace = true
 smallvec.workspace = true
 smol.workspace = true
 task.workspace = true
+telemetry.workspace = true
 util.workspace = true
 workspace-hack.workspace = true
 

crates/dap/src/dap.rs 🔗

@@ -9,7 +9,11 @@ pub mod transport;
 use std::net::Ipv4Addr;
 
 pub use dap_types::*;
+use debugger_settings::DebuggerSettings;
+use gpui::App;
 pub use registry::{DapLocator, DapRegistry};
+use serde::Serialize;
+use settings::Settings;
 pub use task::DebugRequest;
 
 pub type ScopeId = u64;
@@ -18,7 +22,7 @@ pub type StackFrameId = u64;
 
 #[cfg(any(test, feature = "test-support"))]
 pub use adapters::FakeAdapter;
-use task::TcpArgumentsTemplate;
+use task::{DebugScenario, TcpArgumentsTemplate};
 
 pub async fn configure_tcp_connection(
     tcp_connection: TcpArgumentsTemplate,
@@ -34,3 +38,31 @@ pub async fn configure_tcp_connection(
 
     Ok((host, port, timeout))
 }
+
+#[derive(Clone, Copy, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum TelemetrySpawnLocation {
+    Gutter,
+    ScenarioList,
+    Custom,
+}
+
+pub fn send_telemetry(scenario: &DebugScenario, location: TelemetrySpawnLocation, cx: &App) {
+    let Some(adapter) = cx.global::<DapRegistry>().adapter(&scenario.adapter) else {
+        return;
+    };
+    let kind = adapter
+        .validate_config(&scenario.config)
+        .ok()
+        .map(serde_json::to_value)
+        .and_then(Result::ok);
+    let dock = DebuggerSettings::get_global(cx).dock;
+    telemetry::event!(
+        "Debugger Session Started",
+        spawn_location = location,
+        with_build_task = scenario.build.is_some(),
+        kind = kind,
+        adapter = scenario.adapter.as_ref(),
+        dock_position = dock,
+    );
+}

crates/debugger_ui/src/new_session_modal.rs 🔗

@@ -9,7 +9,9 @@ use std::{
     usize,
 };
 
-use dap::{DapRegistry, DebugRequest, adapters::DebugAdapterName};
+use dap::{
+    DapRegistry, DebugRequest, TelemetrySpawnLocation, adapters::DebugAdapterName, send_telemetry,
+};
 use editor::{Editor, EditorElement, EditorStyle};
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
@@ -240,6 +242,7 @@ impl NewSessionModal {
         let Some(task_contexts) = self.task_contexts(cx) else {
             return;
         };
+        send_telemetry(&config, TelemetrySpawnLocation::Custom, cx);
         let task_context = task_contexts.active_context().cloned().unwrap_or_default();
         let worktree_id = task_contexts.worktree();
         cx.spawn_in(window, async move |this, cx| {
@@ -277,8 +280,8 @@ impl NewSessionModal {
         })
     }
 
-    fn task_contexts<'a>(&self, cx: &'a mut Context<Self>) -> Option<&'a TaskContexts> {
-        self.launch_picker.read(cx).delegate.task_contexts.as_ref()
+    fn task_contexts(&self, cx: &App) -> Option<Arc<TaskContexts>> {
+        self.launch_picker.read(cx).delegate.task_contexts.clone()
     }
 
     fn adapter_drop_down_menu(
@@ -901,7 +904,7 @@ pub(super) struct DebugScenarioDelegate {
     matches: Vec<StringMatch>,
     prompt: String,
     debug_panel: WeakEntity<DebugPanel>,
-    task_contexts: Option<TaskContexts>,
+    task_contexts: Option<Arc<TaskContexts>>,
     divider_index: Option<usize>,
     last_used_candidate_index: Option<usize>,
 }
@@ -954,7 +957,7 @@ impl DebugScenarioDelegate {
         _window: &mut Window,
         cx: &mut Context<Picker<Self>>,
     ) {
-        self.task_contexts = Some(task_contexts);
+        self.task_contexts = Some(Arc::new(task_contexts));
 
         let (recent, scenarios) = self
             .task_store
@@ -1090,6 +1093,7 @@ impl PickerDelegate for DebugScenarioDelegate {
             })
             .unwrap_or_default();
 
+        send_telemetry(&debug_scenario, TelemetrySpawnLocation::ScenarioList, cx);
         self.debug_panel
             .update(cx, |panel, cx| {
                 panel.start_session(debug_scenario, task_context, None, worktree_id, window, cx);

crates/editor/Cargo.toml 🔗

@@ -37,6 +37,7 @@ clock.workspace = true
 collections.workspace = true
 command_palette_hooks.workspace = true
 convert_case.workspace = true
+dap.workspace = true
 db.workspace = true
 buffer_diff.workspace = true
 emojis.workspace = true

crates/editor/src/editor.rs 🔗

@@ -59,6 +59,7 @@ use client::{Collaborator, ParticipantIndex};
 use clock::{AGENT_REPLICA_ID, ReplicaId};
 use collections::{BTreeMap, HashMap, HashSet, VecDeque};
 use convert_case::{Case, Casing};
+use dap::TelemetrySpawnLocation;
 use display_map::*;
 pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
 pub use editor_settings::{
@@ -5619,6 +5620,7 @@ impl Editor {
                 let context = actions_menu.actions.context.clone();
 
                 workspace.update(cx, |workspace, cx| {
+                    dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
                     workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
                 });
                 Some(Task::ready(Ok(())))