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