gpui/
profiler.rs

1use std::{
2    cell::LazyCell,
3    hash::Hasher,
4    hash::{DefaultHasher, Hash},
5    sync::Arc,
6    thread::ThreadId,
7    time::Instant,
8};
9
10use serde::{Deserialize, Serialize};
11
12#[doc(hidden)]
13#[derive(Debug, Copy, Clone)]
14pub struct TaskTiming {
15    pub location: &'static core::panic::Location<'static>,
16    pub start: Instant,
17    pub end: Option<Instant>,
18}
19
20#[doc(hidden)]
21#[derive(Debug, Clone)]
22pub struct ThreadTaskTimings {
23    pub thread_name: Option<String>,
24    pub thread_id: ThreadId,
25    pub timings: Vec<TaskTiming>,
26}
27
28impl ThreadTaskTimings {
29    pub(crate) fn convert(timings: &[GlobalThreadTimings]) -> Vec<Self> {
30        timings
31            .iter()
32            .filter_map(|t| match t.timings.upgrade() {
33                Some(timings) => Some((t.thread_id, timings)),
34                _ => None,
35            })
36            .map(|(thread_id, timings)| {
37                let timings = timings.lock();
38                let thread_name = timings.thread_name.clone();
39                let timings = &timings.timings;
40
41                let mut vec = Vec::with_capacity(timings.len());
42
43                let (s1, s2) = timings.as_slices();
44                vec.extend_from_slice(s1);
45                vec.extend_from_slice(s2);
46
47                ThreadTaskTimings {
48                    thread_name,
49                    thread_id,
50                    timings: vec,
51                }
52            })
53            .collect()
54    }
55}
56
57/// Serializable variant of [`core::panic::Location`]
58#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
59pub struct SerializedLocation<'a> {
60    /// Name of the source file
61    pub file: &'a str,
62    /// Line in the source file
63    pub line: u32,
64    /// Column in the source file
65    pub column: u32,
66}
67
68impl<'a> From<&'a core::panic::Location<'a>> for SerializedLocation<'a> {
69    fn from(value: &'a core::panic::Location<'a>) -> Self {
70        SerializedLocation {
71            file: value.file(),
72            line: value.line(),
73            column: value.column(),
74        }
75    }
76}
77
78/// Serializable variant of [`TaskTiming`]
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct SerializedTaskTiming<'a> {
81    /// Location of the timing
82    #[serde(borrow)]
83    pub location: SerializedLocation<'a>,
84    /// Time at which the measurement was reported in nanoseconds
85    pub start: u128,
86    /// Duration of the measurement in nanoseconds
87    pub duration: u128,
88}
89
90impl<'a> SerializedTaskTiming<'a> {
91    /// Convert an array of [`TaskTiming`] into their serializable format
92    ///
93    /// # Params
94    ///
95    /// `anchor` - [`Instant`] that should be earlier than all timings to use as base anchor
96    pub fn convert(anchor: Instant, timings: &[TaskTiming]) -> Vec<SerializedTaskTiming<'static>> {
97        let serialized = timings
98            .iter()
99            .map(|timing| {
100                let start = timing.start.duration_since(anchor).as_nanos();
101                let duration = timing
102                    .end
103                    .unwrap_or_else(|| Instant::now())
104                    .duration_since(timing.start)
105                    .as_nanos();
106                SerializedTaskTiming {
107                    location: timing.location.into(),
108                    start,
109                    duration,
110                }
111            })
112            .collect::<Vec<_>>();
113
114        serialized
115    }
116}
117
118/// Serializable variant of [`ThreadTaskTimings`]
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct SerializedThreadTaskTimings<'a> {
121    /// Thread name
122    pub thread_name: Option<String>,
123    /// Hash of the thread id
124    pub thread_id: u64,
125    /// Timing records for this thread
126    #[serde(borrow)]
127    pub timings: Vec<SerializedTaskTiming<'a>>,
128}
129
130impl<'a> SerializedThreadTaskTimings<'a> {
131    /// Convert [`ThreadTaskTimings`] into their serializable format
132    ///
133    /// # Params
134    ///
135    /// `anchor` - [`Instant`] that should be earlier than all timings to use as base anchor
136    pub fn convert(
137        anchor: Instant,
138        timings: ThreadTaskTimings,
139    ) -> SerializedThreadTaskTimings<'static> {
140        let serialized_timings = SerializedTaskTiming::convert(anchor, &timings.timings);
141
142        let mut hasher = DefaultHasher::new();
143        timings.thread_id.hash(&mut hasher);
144        let thread_id = hasher.finish();
145
146        SerializedThreadTaskTimings {
147            thread_name: timings.thread_name,
148            thread_id,
149            timings: serialized_timings,
150        }
151    }
152}
153
154// Allow 20mb of task timing entries
155const MAX_TASK_TIMINGS: usize = (20 * 1024 * 1024) / core::mem::size_of::<TaskTiming>();
156
157pub(crate) type TaskTimings = circular_buffer::CircularBuffer<MAX_TASK_TIMINGS, TaskTiming>;
158pub(crate) type GuardedTaskTimings = spin::Mutex<ThreadTimings>;
159
160pub(crate) struct GlobalThreadTimings {
161    pub thread_id: ThreadId,
162    pub timings: std::sync::Weak<GuardedTaskTimings>,
163}
164
165pub(crate) static GLOBAL_THREAD_TIMINGS: spin::Mutex<Vec<GlobalThreadTimings>> =
166    spin::Mutex::new(Vec::new());
167
168thread_local! {
169    pub(crate) static THREAD_TIMINGS: LazyCell<Arc<GuardedTaskTimings>> = LazyCell::new(|| {
170        let current_thread = std::thread::current();
171        let thread_name = current_thread.name();
172        let thread_id = current_thread.id();
173        let timings = ThreadTimings::new(thread_name.map(|e| e.to_string()), thread_id);
174        let timings = Arc::new(spin::Mutex::new(timings));
175
176        {
177            let timings = Arc::downgrade(&timings);
178            let global_timings = GlobalThreadTimings {
179                thread_id: std::thread::current().id(),
180                timings,
181            };
182            GLOBAL_THREAD_TIMINGS.lock().push(global_timings);
183        }
184
185        timings
186    });
187}
188
189pub(crate) struct ThreadTimings {
190    pub thread_name: Option<String>,
191    pub thread_id: ThreadId,
192    pub timings: Box<TaskTimings>,
193}
194
195impl ThreadTimings {
196    pub(crate) fn new(thread_name: Option<String>, thread_id: ThreadId) -> Self {
197        ThreadTimings {
198            thread_name,
199            thread_id,
200            timings: TaskTimings::boxed(),
201        }
202    }
203}
204
205impl Drop for ThreadTimings {
206    fn drop(&mut self) {
207        let mut thread_timings = GLOBAL_THREAD_TIMINGS.lock();
208
209        let Some((index, _)) = thread_timings
210            .iter()
211            .enumerate()
212            .find(|(_, t)| t.thread_id == self.thread_id)
213        else {
214            return;
215        };
216        thread_timings.swap_remove(index);
217    }
218}
219
220pub(crate) fn add_task_timing(timing: TaskTiming) {
221    THREAD_TIMINGS.with(|timings| {
222        let mut timings = timings.lock();
223        let timings = &mut timings.timings;
224
225        if let Some(last_timing) = timings.iter_mut().rev().next() {
226            if last_timing.location == timing.location {
227                last_timing.end = timing.end;
228                return;
229            }
230        }
231
232        timings.push_back(timing);
233    });
234}