Add dev::StartTracing action to launch Tracy profiler

Richard Feldman created

- Add is_enabled() function to ztracing crate to detect if tracy support is compiled in
- Add StartTracing action that launches tracy-profiler if found on PATH
- Show appropriate notifications for success/failure states
- Show warning with icon when running debug build (profiling results won't be accurate)
- Simplify ztracing to use just --features tracy instead of requiring ZTRACING env var

Change summary

Cargo.lock                 |   1 
crates/zed/Cargo.toml      |   1 
crates/zed/src/zed.rs      | 140 ++++++++++++++++++++++++++++++++++++++++
crates/ztracing/build.rs   |   6 -
crates/ztracing/src/lib.rs |  46 +++++++++----
5 files changed, 174 insertions(+), 20 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -20786,6 +20786,7 @@ dependencies = [
  "watch",
  "web_search",
  "web_search_providers",
+ "which 6.0.3",
  "which_key",
  "windows 0.61.3",
  "winresource",

crates/zed/Cargo.toml 🔗

@@ -159,6 +159,7 @@ vim_mode_setting.workspace = true
 watch.workspace = true
 web_search.workspace = true
 web_search_providers.workspace = true
+which.workspace = true
 which_key.workspace = true
 workspace.workspace = true
 zed_actions.workspace = true

crates/zed/src/zed.rs 🔗

@@ -139,6 +139,10 @@ actions!(
         /// audio system (including yourself) on the current call in a tar file
         /// in the current working directory.
         CaptureRecentAudio,
+        /// Starts Tracy profiling by launching tracy-profiler if available.
+        /// Tracy will connect to this Zed instance and begin capturing performance data.
+        /// Requires Zed to be built with tracy support (ZTRACING=1).
+        StartTracing,
     ]
 );
 
@@ -1148,6 +1152,9 @@ fn register_actions(
         })
         .register_action(|workspace, _: &CaptureRecentAudio, window, cx| {
             capture_recent_audio(workspace, window, cx);
+        })
+        .register_action(|workspace, _: &StartTracing, window, cx| {
+            start_tracing(workspace, window, cx);
         });
 
     #[cfg(not(target_os = "windows"))]
@@ -2211,6 +2218,139 @@ fn capture_recent_audio(workspace: &mut Workspace, _: &mut Window, cx: &mut Cont
     );
 }
 
+fn start_tracing(workspace: &mut Workspace, _window: &mut Window, cx: &mut Context<Workspace>) {
+    struct StartTracingNotification {
+        focus_handle: gpui::FocusHandle,
+        status: TracingStatus,
+        _spawn_task: Option<Task<()>>,
+    }
+
+    enum TracingStatus {
+        Starting,
+        TracyLaunched,
+        TracyNotFound,
+        TracyLaunchFailed(String),
+        ZtracingNotEnabled,
+    }
+
+    impl gpui::EventEmitter<DismissEvent> for StartTracingNotification {}
+    impl gpui::EventEmitter<SuppressEvent> for StartTracingNotification {}
+    impl gpui::Focusable for StartTracingNotification {
+        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
+            self.focus_handle.clone()
+        }
+    }
+    impl workspace::notifications::Notification for StartTracingNotification {}
+
+    impl Render for StartTracingNotification {
+        fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+            let (title, message) = match &self.status {
+                TracingStatus::Starting => (
+                    "Starting Tracy",
+                    "Launching tracy-profiler...".to_string(),
+                ),
+                TracingStatus::TracyLaunched => (
+                    "Tracy Profiler Ready",
+                    "Tracy profiler has been launched. It should automatically connect to this Zed instance and begin capturing performance data.".to_string(),
+                ),
+                TracingStatus::TracyNotFound => (
+                    "Tracy Not Found",
+                    "Could not find `tracy-profiler` on your PATH. Please install Tracy profiler and ensure it's available in your PATH.\n\nOn macOS: brew install tracy\nOn Linux: Install from your package manager or build from source".to_string(),
+                ),
+                TracingStatus::TracyLaunchFailed(error) => (
+                    "Tracy Launch Failed",
+                    format!("Failed to launch tracy-profiler: {}", error),
+                ),
+                TracingStatus::ZtracingNotEnabled => (
+                    "Tracy Support Not Enabled",
+                    "This build of Zed was not compiled with Tracy support. To enable tracing, rebuild Zed with `cargo build --features tracy`.".to_string(),
+                ),
+            };
+
+            let show_debug_warning =
+                cfg!(debug_assertions) && matches!(self.status, TracingStatus::TracyLaunched);
+
+            NotificationFrame::new()
+                .with_title(Some(title))
+                .show_suppress_button(false)
+                .on_close(cx.listener(|_, _, _, cx| {
+                    cx.emit(DismissEvent);
+                }))
+                .with_content(
+                    v_flex()
+                        .gap_2()
+                        .child(Label::new(message).size(ui::LabelSize::Small))
+                        .when(show_debug_warning, |this| {
+                            this.child(
+                                h_flex()
+                                    .gap_1()
+                                    .child(
+                                        ui::Icon::new(ui::IconName::Warning)
+                                            .size(ui::IconSize::Small)
+                                            .color(ui::Color::Warning),
+                                    )
+                                    .child(
+                                        Label::new(
+                                            "This is a debug build of Zed, which will be much slower than a release build.",
+                                        )
+                                        .size(ui::LabelSize::Small)
+                                        .color(ui::Color::Warning),
+                                    ),
+                            )
+                        }),
+                )
+        }
+    }
+
+    impl StartTracingNotification {
+        fn new(cx: &mut Context<Self>) -> Self {
+            if !ztracing::is_enabled() {
+                return Self {
+                    focus_handle: cx.focus_handle(),
+                    status: TracingStatus::ZtracingNotEnabled,
+                    _spawn_task: None,
+                };
+            }
+
+            let spawn_task = cx.spawn(async move |this, cx| {
+                let tracy_path = cx
+                    .background_spawn(async { which::which("tracy-profiler").ok() })
+                    .await;
+
+                let status = match tracy_path {
+                    Some(path) => {
+                        let spawn_result = smol::process::Command::new(&path).spawn();
+
+                        match spawn_result {
+                            Ok(_child) => TracingStatus::TracyLaunched,
+                            Err(error) => TracingStatus::TracyLaunchFailed(error.to_string()),
+                        }
+                    }
+                    None => TracingStatus::TracyNotFound,
+                };
+
+                this.update(cx, |this, cx| {
+                    this.status = status;
+                    cx.notify();
+                })
+                .ok();
+            });
+
+            Self {
+                focus_handle: cx.focus_handle(),
+                status: TracingStatus::Starting,
+                _spawn_task: Some(spawn_task),
+            }
+        }
+    }
+
+    workspace.show_notification(
+        NotificationId::unique::<StartTracingNotification>(),
+        cx,
+        |cx| cx.new(StartTracingNotification::new),
+    );
+}
+
 /// Eagerly loads the active theme and icon theme based on the selections in the
 /// theme settings.
 ///

crates/ztracing/build.rs 🔗

@@ -1,9 +1,3 @@
-use std::env;
-
 fn main() {
-    if env::var_os("ZTRACING").is_some() {
-        println!(r"cargo::rustc-cfg=ztracing");
-    }
     println!("cargo::rerun-if-changed=build.rs");
-    println!("cargo::rerun-if-env-changed=ZTRACING");
 }

crates/ztracing/src/lib.rs 🔗

@@ -1,28 +1,28 @@
 pub use tracing::{Level, field};
 
-#[cfg(ztracing)]
+#[cfg(feature = "tracy")]
 pub use tracing::{
     Span, debug_span, error_span, event, info_span, instrument, span, trace_span, warn_span,
 };
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub use ztracing_macro::instrument;
 
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub use __consume_all_tokens as trace_span;
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub use __consume_all_tokens as info_span;
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub use __consume_all_tokens as debug_span;
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub use __consume_all_tokens as warn_span;
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub use __consume_all_tokens as error_span;
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub use __consume_all_tokens as event;
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub use __consume_all_tokens as span;
 
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 #[macro_export]
 macro_rules! __consume_all_tokens {
     ($($t:tt)*) => {
@@ -30,10 +30,10 @@ macro_rules! __consume_all_tokens {
     };
 }
 
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub struct Span;
 
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 impl Span {
     pub fn current() -> Self {
         Self
@@ -44,7 +44,7 @@ impl Span {
     pub fn record<T, S>(&self, _t: T, _s: S) {}
 }
 
-#[cfg(ztracing)]
+#[cfg(feature = "tracy")]
 pub fn init() {
     zlog::info!("Starting tracy subscriber, you can now connect the profiler");
     use tracing_subscriber::prelude::*;
@@ -54,5 +54,23 @@ pub fn init() {
     .expect("setup tracy layer");
 }
 
-#[cfg(not(ztracing))]
+#[cfg(not(feature = "tracy"))]
 pub fn init() {}
+
+/// Returns true if this build was compiled with Tracy profiling support.
+///
+/// When true, `init()` will set up the Tracy subscriber and the application
+/// can be profiled by connecting Tracy profiler to it.
+#[cfg(feature = "tracy")]
+pub const fn is_enabled() -> bool {
+    true
+}
+
+/// Returns true if this build was compiled with Tracy profiling support.
+///
+/// When true, `init()` will set up the Tracy subscriber and the application
+/// can be profiled by connecting Tracy profiler to it.
+#[cfg(not(feature = "tracy"))]
+pub const fn is_enabled() -> bool {
+    false
+}