Integrate profiling into gpui (#8176)

Dzmitry Malyshau created

[Profiling](https://crates.io/crates/profiling) crate allows easy
integration with various profiler tools. The best thing is - annotations
compile to nothing unless you request a specific feature.

For example, I used this command to enable Tracy support:
```bash
cargo run --features profiling/profile-with-tracy
```
At the same time I had Tracy tool open and waiting for connection. It
gathered nice stats from the run:

![zed-profiler](https://github.com/zed-industries/zed/assets/107301/5233045d-078c-4ad8-8b00-7ae55cf94ebb)


Release Notes:
- N/A

Change summary

Cargo.lock                                       | 21 ++++++++++++++++++
Cargo.toml                                       |  1 
crates/gpui/Cargo.toml                           |  1 
crates/gpui/src/platform/blade/blade_atlas.rs    |  1 
crates/gpui/src/platform/blade/blade_belt.rs     |  1 
crates/gpui/src/platform/blade/blade_renderer.rs |  8 ++++++
crates/gpui/src/platform/linux/dispatcher.rs     |  1 
crates/gpui/src/platform/linux/x11/client.rs     |  8 ++++++
crates/gpui/src/window.rs                        |  4 +++
crates/zed/Cargo.toml                            |  1 
10 files changed, 45 insertions(+), 2 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4121,6 +4121,7 @@ dependencies = [
  "pathfinder_geometry",
  "png",
  "postage",
+ "profiling",
  "rand 0.8.5",
  "raw-window-handle 0.5.2",
  "raw-window-handle 0.6.0",
@@ -6863,6 +6864,25 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "profiling"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
+dependencies = [
+ "profiling-procmacros",
+]
+
+[[package]]
+name = "profiling-procmacros"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
+dependencies = [
+ "quote",
+ "syn 2.0.48",
+]
+
 [[package]]
 name = "project"
 version = "0.1.0"
@@ -11978,6 +11998,7 @@ dependencies = [
  "outline",
  "parking_lot 0.11.2",
  "postage",
+ "profiling",
  "project",
  "project_panel",
  "project_symbols",

Cargo.toml 🔗

@@ -203,6 +203,7 @@ linkify = "0.10.0"
 log = { version = "0.4.16", features = ["kv_unstable_serde"] }
 ordered-float = "2.1.1"
 parking_lot = "0.11.1"
+profiling = "1"
 postage = { version = "0.5", features = ["futures-traits"] }
 pretty_assertions = "1.3.0"
 prost = "0.8"

crates/gpui/Cargo.toml 🔗

@@ -51,6 +51,7 @@ parking = "2.0.0"
 parking_lot.workspace = true
 pathfinder_geometry = "0.5"
 postage.workspace = true
+profiling.workspace = true
 rand.workspace = true
 raw-window-handle = "0.6"
 refineable.workspace = true

crates/gpui/src/platform/blade/blade_atlas.rs 🔗

@@ -117,6 +117,7 @@ impl PlatformAtlas for BladeAtlas {
         if let Some(tile) = lock.tiles_by_key.get(key) {
             Ok(tile.clone())
         } else {
+            profiling::scope!("new tile");
             let (size, bytes) = build()?;
             let tile = lock.allocate(size, key.texture_kind());
             lock.upload_texture(tile.texture_id, tile.bounds, &bytes);

crates/gpui/src/platform/blade/blade_belt.rs 🔗

@@ -39,6 +39,7 @@ impl BladeBelt {
         }
     }
 
+    #[profiling::function]
     pub fn alloc(&mut self, size: u64, gpu: &gpu::Context) -> gpu::BufferPiece {
         for &mut (ref rb, ref mut offset) in self.active.iter_mut() {
             let aligned = offset.next_multiple_of(self.desc.alignment);

crates/gpui/src/platform/blade/blade_renderer.rs 🔗

@@ -444,6 +444,7 @@ impl BladeRenderer {
         self.gpu.metal_layer().unwrap().as_ptr()
     }
 
+    #[profiling::function]
     fn rasterize_paths(&mut self, paths: &[Path<ScaledPixels>]) {
         self.path_tiles.clear();
         let mut vertices_by_texture_id = HashMap::default();
@@ -506,7 +507,10 @@ impl BladeRenderer {
     }
 
     pub fn draw(&mut self, scene: &Scene) {
-        let frame = self.gpu.acquire_frame();
+        let frame = {
+            profiling::scope!("acquire frame");
+            self.gpu.acquire_frame()
+        };
         self.command_encoder.start();
         self.command_encoder.init_texture(frame.texture());
 
@@ -529,6 +533,7 @@ impl BladeRenderer {
             }],
             depth_stencil: None,
         }) {
+            profiling::scope!("render pass");
             for batch in scene.batches() {
                 match batch {
                     PrimitiveBatch::Quads(quads) => {
@@ -718,6 +723,7 @@ impl BladeRenderer {
         self.command_encoder.present(frame);
         let sync_point = self.gpu.submit(&mut self.command_encoder);
 
+        profiling::scope!("finish");
         self.instance_belt.flush(&sync_point);
         self.atlas.after_frame(&sync_point);
         self.atlas.clear_textures(AtlasTextureKind::Path);

crates/gpui/src/platform/linux/dispatcher.rs 🔗

@@ -33,6 +33,7 @@ impl LinuxDispatcher {
     ) -> Self {
         let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
         let background_thread = thread::spawn(move || {
+            profiling::register_thread!("background");
             for runnable in background_receiver {
                 let _ignore_panic = panic::catch_unwind(|| runnable.run());
             }

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -71,7 +71,10 @@ impl Client for X11Client {
         // into window functions as they may invoke callbacks that need
         // to immediately access the platform (self).
         while !self.platform_inner.state.lock().quit_requested {
-            let event = self.xcb_connection.wait_for_event().unwrap();
+            let event = {
+                profiling::scope!("Wait for event");
+                self.xcb_connection.wait_for_event().unwrap()
+            };
             match event {
                 xcb::Event::X(x::Event::ClientMessage(ev)) => {
                     if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
@@ -210,6 +213,7 @@ impl Client for X11Client {
                 _ => {}
             }
 
+            profiling::scope!("Runnables");
             if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
                 runnable.run();
             }
@@ -219,6 +223,7 @@ impl Client for X11Client {
             fun();
         }
     }
+
     fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
         let setup = self.xcb_connection.get_setup();
         setup
@@ -230,6 +235,7 @@ impl Client for X11Client {
             })
             .collect()
     }
+
     fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
         Some(Rc::new(X11Display::new(&self.xcb_connection, id.0 as i32)))
     }

crates/gpui/src/window.rs 🔗

@@ -950,6 +950,7 @@ impl<'a> WindowContext<'a> {
 
     /// Produces a new frame and assigns it to `rendered_frame`. To actually show
     /// the contents of the new [Scene], use [present].
+    #[profiling::function]
     pub fn draw(&mut self) {
         self.window.dirty.set(false);
         self.window.drawing = true;
@@ -1092,11 +1093,13 @@ impl<'a> WindowContext<'a> {
         self.window.needs_present.set(true);
     }
 
+    #[profiling::function]
     fn present(&self) {
         self.window
             .platform_window
             .draw(&self.window.rendered_frame.scene);
         self.window.needs_present.set(false);
+        profiling::finish_frame!();
     }
 
     /// Dispatch a given keystroke as though the user had typed it.
@@ -1132,6 +1135,7 @@ impl<'a> WindowContext<'a> {
     }
 
     /// Dispatch a mouse or keyboard event on the window.
+    #[profiling::function]
     pub fn dispatch_event(&mut self, event: PlatformInput) -> bool {
         self.window.last_input_timestamp.set(Instant::now());
         // Handlers may set this to false by calling `stop_propagation`.

crates/zed/Cargo.toml 🔗

@@ -77,6 +77,7 @@ num_cpus = "1.13.0"
 outline.workspace = true
 parking_lot.workspace = true
 postage.workspace = true
+profiling.workspace = true
 project.workspace = true
 project_panel.workspace = true
 project_symbols.workspace = true