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}