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}