1use std::{
2 num::{NonZeroU64, NonZeroUsize},
3 sync::{Arc, OnceLock},
4};
5
6use hashbrown::HashMap;
7use smallvec::SmallVec;
8use turbo_rcstr::RcStr;
9
10use crate::{lazy_sorted_vec::LazySortedVec, timestamp::Timestamp};
11
12pub type SpanIndex = NonZeroUsize;
13
14pub type SpanArgs = SmallVec<[(RcStr, RcStr); 1]>;
18
19pub struct Span {
20 pub parent: Option<SpanIndex>,
22 pub depth: u32,
23 pub start: Timestamp,
24 pub category: RcStr,
25 pub name: RcStr,
26 pub args: SpanArgs,
27
28 pub events: LazySortedVec<SpanEvent>,
33 pub is_complete: bool,
34
35 pub self_allocations: u64,
37 pub self_allocation_count: u64,
38 pub self_deallocations: u64,
39 pub self_deallocation_count: u64,
40
41 pub totals: OnceLock<SpanTotals>,
45 pub time_data: SpanTimeData,
46 pub extra: OnceLock<Box<SpanExtra>>,
47 pub names: OnceLock<SpanNames>,
51}
52
53#[derive(Default)]
54pub struct SpanTotals {
55 pub max_depth: u32,
56 pub allocations: u64,
57 pub deallocations: u64,
58 pub persistent_allocations: u64,
59 pub allocation_count: u64,
60 pub span_count: u64,
61}
62
63#[derive(Default)]
64pub struct SpanTimeData {
65 pub ignore_self_time: bool,
67
68 pub self_end: Timestamp,
70
71 pub self_time: Timestamp,
73
74 pub end: OnceLock<Timestamp>,
76 pub total_time: OnceLock<Timestamp>,
77 pub corrected_self_time: OnceLock<Timestamp>,
78 pub corrected_total_time: OnceLock<Timestamp>,
79}
80
81#[derive(Default)]
82pub struct SpanExtra {
83 pub graph: OnceLock<Vec<SpanGraphEvent>>,
84 pub bottom_up: OnceLock<Vec<Arc<SpanBottomUp>>>,
85 pub search_index: OnceLock<HashMap<RcStr, Vec<SpanIndex>>>,
86}
87
88#[derive(Clone)]
89pub struct SpanName {
90 pub category: RcStr,
91 pub title: RcStr,
92}
93
94pub struct SpanNames {
95 pub nice_name: SpanName,
96 pub group_name: SpanName,
97}
98
99impl Span {
100 pub fn extra(&self) -> &SpanExtra {
101 self.extra.get_or_init(Default::default)
102 }
103
104 pub fn names(&self) -> &SpanNames {
105 self.names.get_or_init(|| self.compute_names())
106 }
107
108 fn compute_names(&self) -> SpanNames {
109 enum Kind {
113 Function,
114 Resolve,
115 Other,
116 }
117 let kind = match self.name.as_str() {
118 "turbo_tasks::function" => Kind::Function,
119 "turbo_tasks::resolve_call" | "turbo_tasks::resolve_trait_call" => Kind::Resolve,
120 _ => Kind::Other,
121 };
122 let arg_name = self.args.iter().find(|&(k, _)| k == "name").map(|(_, v)| v);
123
124 let generic = || SpanName {
126 category: self.category.clone(),
127 title: self.name.clone(),
128 };
129
130 match (kind, arg_name) {
137 (Kind::Function, Some(n)) => {
138 let pretty = SpanName {
139 category: self.name.clone(),
140 title: n.clone(),
141 };
142 SpanNames {
143 nice_name: pretty.clone(),
144 group_name: pretty,
145 }
146 }
147 (Kind::Resolve, Some(n)) => SpanNames {
148 nice_name: SpanName {
149 category: self.name.clone(),
150 title: format!("*{n}").into(),
151 },
152 group_name: SpanName {
153 category: self.category.clone(),
154 title: format!("{} *{n}", self.name).into(),
155 },
156 },
157 (Kind::Other, Some(n)) => SpanNames {
158 nice_name: SpanName {
159 category: self.category.clone(),
160 title: format!("{} {n}", self.name).into(),
161 },
162 group_name: generic(),
163 },
164 (_, None) => SpanNames {
165 nice_name: generic(),
166 group_name: generic(),
167 },
168 }
169 }
170}
171
172pub struct SpanEventSelfTime {
178 pub start: Timestamp,
179 pub duration: NonZeroU64,
180 pub corrected_self_time: OnceLock<Timestamp>,
181}
182
183impl SpanEventSelfTime {
184 pub fn end(&self) -> Timestamp {
185 Timestamp::from_value(*self.start + self.duration.get())
186 }
187}
188
189pub enum SpanEvent {
190 SelfTime(SpanEventSelfTime),
191 Child { start: Timestamp, index: SpanIndex },
192}
193
194const _: () = assert!(std::mem::size_of::<SpanEvent>() == 32);
198
199impl SpanEvent {
200 pub fn self_time(start: Timestamp, end: Timestamp) -> Option<Self> {
203 let duration = NonZeroU64::new(*end.saturating_sub(start))?;
204 Some(SpanEvent::SelfTime(SpanEventSelfTime {
205 start,
206 duration,
207 corrected_self_time: OnceLock::new(),
208 }))
209 }
210
211 pub fn start(&self) -> Timestamp {
212 match self {
213 SpanEvent::SelfTime(self_time) => self_time.start,
214 SpanEvent::Child { start, .. } => *start,
215 }
216 }
217}
218
219impl PartialEq for SpanEvent {
220 fn eq(&self, other: &Self) -> bool {
221 self.cmp(other) == std::cmp::Ordering::Equal
222 }
223}
224
225impl Eq for SpanEvent {}
226
227impl PartialOrd for SpanEvent {
228 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
229 Some(self.cmp(other))
230 }
231}
232
233impl Ord for SpanEvent {
234 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
235 self.start()
236 .cmp(&other.start())
237 .then_with(|| match (self, other) {
238 (SpanEvent::SelfTime(_), SpanEvent::Child { .. }) => std::cmp::Ordering::Less,
239 (SpanEvent::Child { .. }, SpanEvent::SelfTime(_)) => std::cmp::Ordering::Greater,
240 (SpanEvent::SelfTime(a), SpanEvent::SelfTime(b)) => a.duration.cmp(&b.duration),
241 (
242 SpanEvent::Child { start: _, index: a },
243 SpanEvent::Child { start: _, index: b },
244 ) => a.cmp(b),
245 })
246 }
247}
248
249#[derive(Clone)]
250pub enum SpanGraphEvent {
251 #[allow(dead_code)]
253 SelfTime {
254 duration: Timestamp,
255 },
256 Child {
257 child: Arc<SpanGraph>,
258 },
259}
260
261pub struct SpanGraph {
262 pub root_spans: Vec<SpanIndex>,
264 pub recursive_spans: Vec<SpanIndex>,
265
266 pub max_depth: OnceLock<u32>,
268 pub events: OnceLock<Vec<SpanGraphEvent>>,
269 pub self_time: OnceLock<Timestamp>,
270 pub self_allocations: OnceLock<u64>,
271 pub self_deallocations: OnceLock<u64>,
272 pub self_persistent_allocations: OnceLock<u64>,
273 pub self_allocation_count: OnceLock<u64>,
274 pub total_time: OnceLock<Timestamp>,
275 pub total_allocations: OnceLock<u64>,
276 pub total_deallocations: OnceLock<u64>,
277 pub total_persistent_allocations: OnceLock<u64>,
278 pub total_allocation_count: OnceLock<u64>,
279 pub total_span_count: OnceLock<u64>,
280 pub corrected_self_time: OnceLock<Timestamp>,
281 pub corrected_total_time: OnceLock<Timestamp>,
282 pub bottom_up: OnceLock<Vec<Arc<SpanBottomUp>>>,
283}
284
285pub struct SpanBottomUp {
286 pub self_spans: Vec<SpanIndex>,
288 pub children: Vec<Arc<SpanBottomUp>>,
289 pub example_span: SpanIndex,
290
291 pub max_depth: OnceLock<u32>,
293 pub events: OnceLock<Vec<SpanGraphEvent>>,
294 pub self_time: OnceLock<Timestamp>,
295 pub corrected_self_time: OnceLock<Timestamp>,
296 pub self_allocations: OnceLock<u64>,
297 pub self_deallocations: OnceLock<u64>,
298 pub self_persistent_allocations: OnceLock<u64>,
299 pub self_allocation_count: OnceLock<u64>,
300}
301
302impl SpanBottomUp {
303 pub fn new(
304 self_spans: Vec<SpanIndex>,
305 example_span: SpanIndex,
306 children: Vec<Arc<SpanBottomUp>>,
307 ) -> Self {
308 Self {
309 self_spans,
310 children,
311 example_span,
312 max_depth: OnceLock::new(),
313 events: OnceLock::new(),
314 self_time: OnceLock::new(),
315 corrected_self_time: OnceLock::new(),
316 self_allocations: OnceLock::new(),
317 self_deallocations: OnceLock::new(),
318 self_persistent_allocations: OnceLock::new(),
319 self_allocation_count: OnceLock::new(),
320 }
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn span_event_self_time_filters_zero_duration() {
330 let t = Timestamp::from_micros(100);
331 assert!(SpanEvent::self_time(t, t).is_none());
332 assert!(SpanEvent::self_time(t, Timestamp::from_micros(50)).is_none());
334 }
335
336 #[test]
337 fn span_event_self_time_constructs_positive_duration() {
338 let start = Timestamp::from_micros(100);
339 let end = Timestamp::from_micros(150);
340 let event = SpanEvent::self_time(start, end).unwrap();
341 match event {
342 SpanEvent::SelfTime(self_time) => {
343 assert_eq!(self_time.start, start);
344 assert_eq!(self_time.duration.get(), *end - *start);
345 assert_eq!(self_time.end(), end);
346 }
347 SpanEvent::Child { .. } => panic!("expected SelfTime"),
348 }
349 }
350
351 #[test]
352 fn span_event_size_is_packed() {
353 assert_eq!(std::mem::size_of::<SpanEvent>(), 32);
356 }
357}