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}