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}