performance.rs

  1use std::time::Instant;
  2
  3use anyhow::Result;
  4use gpui::{
  5    div, AppContext, InteractiveElement as _, Render, StatefulInteractiveElement as _,
  6    Subscription, ViewContext, VisualContext,
  7};
  8use schemars::JsonSchema;
  9use serde::{Deserialize, Serialize};
 10use settings::{Settings, SettingsSources, SettingsStore};
 11use workspace::{
 12    ui::{Label, LabelCommon, LabelSize, Tooltip},
 13    ItemHandle, StatusItemView, Workspace,
 14};
 15
 16const SHOW_STARTUP_TIME_DURATION: std::time::Duration = std::time::Duration::from_secs(5);
 17
 18pub fn init(cx: &mut AppContext) {
 19    PerformanceSettings::register(cx);
 20
 21    let mut enabled = PerformanceSettings::get_global(cx)
 22        .show_in_status_bar
 23        .unwrap_or(false);
 24    let start_time = Instant::now();
 25    let mut _observe_workspaces = toggle_status_bar_items(enabled, start_time, cx);
 26
 27    cx.observe_global::<SettingsStore>(move |cx| {
 28        let new_value = PerformanceSettings::get_global(cx)
 29            .show_in_status_bar
 30            .unwrap_or(false);
 31        if new_value != enabled {
 32            enabled = new_value;
 33            _observe_workspaces = toggle_status_bar_items(enabled, start_time, cx);
 34        }
 35    })
 36    .detach();
 37}
 38
 39fn toggle_status_bar_items(
 40    enabled: bool,
 41    start_time: Instant,
 42    cx: &mut AppContext,
 43) -> Option<Subscription> {
 44    for window in cx.windows() {
 45        if let Some(workspace) = window.downcast::<Workspace>() {
 46            workspace
 47                .update(cx, |workspace, cx| {
 48                    toggle_status_bar_item(workspace, enabled, start_time, cx);
 49                })
 50                .ok();
 51        }
 52    }
 53
 54    if enabled {
 55        log::info!("performance metrics display enabled");
 56        Some(cx.observe_new_views::<Workspace>(move |workspace, cx| {
 57            toggle_status_bar_item(workspace, true, start_time, cx);
 58        }))
 59    } else {
 60        log::info!("performance metrics display disabled");
 61        None
 62    }
 63}
 64
 65struct PerformanceStatusBarItem {
 66    display_mode: DisplayMode,
 67}
 68
 69#[derive(Copy, Clone, Debug)]
 70enum DisplayMode {
 71    StartupTime,
 72    Fps,
 73}
 74
 75impl PerformanceStatusBarItem {
 76    fn new(start_time: Instant, cx: &mut ViewContext<Self>) -> Self {
 77        let now = Instant::now();
 78        let display_mode = if now < start_time + SHOW_STARTUP_TIME_DURATION {
 79            DisplayMode::StartupTime
 80        } else {
 81            DisplayMode::Fps
 82        };
 83
 84        let this = Self { display_mode };
 85
 86        if let DisplayMode::StartupTime = display_mode {
 87            cx.spawn(|this, mut cx| async move {
 88                let now = Instant::now();
 89                let remaining_duration =
 90                    (start_time + SHOW_STARTUP_TIME_DURATION).saturating_duration_since(now);
 91                cx.background_executor().timer(remaining_duration).await;
 92                this.update(&mut cx, |this, cx| {
 93                    this.display_mode = DisplayMode::Fps;
 94                    cx.notify();
 95                })
 96                .ok();
 97            })
 98            .detach();
 99        }
100
101        this
102    }
103}
104
105impl Render for PerformanceStatusBarItem {
106    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
107        let text = match self.display_mode {
108            DisplayMode::StartupTime => cx
109                .time_to_first_window_draw()
110                .map_or("Pending".to_string(), |duration| {
111                    format!("{}ms", duration.as_millis())
112                }),
113            DisplayMode::Fps => cx.fps().map_or("".to_string(), |fps| {
114                format!("{:3} FPS", fps.round() as u32)
115            }),
116        };
117
118        use gpui::ParentElement;
119        let display_mode = self.display_mode;
120        div()
121            .id("performance status")
122            .child(Label::new(text).size(LabelSize::Small))
123            .tooltip(move |cx| match display_mode {
124                DisplayMode::StartupTime => Tooltip::text("Time to first window draw", cx),
125                DisplayMode::Fps => cx
126                    .new_view(|cx| {
127                        let tooltip = Tooltip::new("Current FPS");
128                        if let Some(time_to_first) = cx.time_to_first_window_draw() {
129                            tooltip.meta(format!(
130                                "Time to first window draw: {}ms",
131                                time_to_first.as_millis()
132                            ))
133                        } else {
134                            tooltip
135                        }
136                    })
137                    .into(),
138            })
139    }
140}
141
142impl StatusItemView for PerformanceStatusBarItem {
143    fn set_active_pane_item(
144        &mut self,
145        _active_pane_item: Option<&dyn ItemHandle>,
146        _cx: &mut gpui::ViewContext<Self>,
147    ) {
148        // This is not currently used.
149    }
150}
151
152fn toggle_status_bar_item(
153    workspace: &mut Workspace,
154    enabled: bool,
155    start_time: Instant,
156    cx: &mut ViewContext<Workspace>,
157) {
158    if enabled {
159        workspace.status_bar().update(cx, |bar, cx| {
160            bar.add_right_item(
161                cx.new_view(|cx| PerformanceStatusBarItem::new(start_time, cx)),
162                cx,
163            )
164        });
165    } else {
166        workspace.status_bar().update(cx, |bar, cx| {
167            bar.remove_items_of_type::<PerformanceStatusBarItem>(cx);
168        });
169    }
170}
171
172/// Configuration of the display of performance details.
173#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
174pub struct PerformanceSettings {
175    /// Display the time to first window draw and frame rate in the status bar.
176    ///
177    /// Default: false
178    pub show_in_status_bar: Option<bool>,
179}
180
181impl Settings for PerformanceSettings {
182    const KEY: Option<&'static str> = Some("performance");
183
184    type FileContent = Self;
185
186    fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
187        sources.json_merge()
188    }
189}