1#![feature(box_patterns)]
2#![feature(bufreader_peek)]
3
4use std::{
5 hash::BuildHasherDefault,
6 path::PathBuf,
7 sync::Arc,
8 thread,
9 time::{Duration, Instant},
10};
11
12use rustc_hash::FxHasher;
13
14use self::{
15 reader::TraceReader, server::serve, span_graph_ref::SpanGraphEventRef, span_ref::SpanRef,
16 store_container::StoreContainer,
17};
18
19mod bottom_up;
20mod chunked_vec;
21mod lazy_sorted_vec;
22mod reader;
23mod self_time_tree;
24mod server;
25mod span;
26mod span_bottom_up_ref;
27mod span_graph_ref;
28mod span_ref;
29mod store;
30pub mod store_container;
31mod string_tuple_ref;
32mod timestamp;
33mod u64_empty_string;
34mod u64_string;
35mod viewer;
36
37#[allow(
38 dead_code,
39 reason = "It's actually used, not sure why it is marked as dead code"
40)]
41type FxIndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<FxHasher>>;
42
43pub fn start_turbopack_trace_server(path: PathBuf, port: Option<u16>) -> Arc<StoreContainer> {
46 let store = Arc::new(StoreContainer::new());
47
48 let store_for_reader = store.clone();
49 let store_for_server = store.clone();
50
51 TraceReader::spawn(store_for_reader, path);
52
53 thread::spawn(move || {
54 serve(store_for_server, port.unwrap_or(5747));
55 });
56
57 store
58}
59
60const PAGE_SIZE: usize = 20;
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub enum SortMode {
65 #[default]
67 ExecutionOrder,
68 Value,
70 Name,
72}
73
74pub struct QueryOptions {
76 pub parent: Option<String>,
79 pub aggregated: bool,
81 pub sort: SortMode,
83 pub search: Option<String>,
85 pub page: usize,
87}
88
89pub struct SpanInfo {
91 pub id: String,
102 pub name: String,
104 pub cpu_duration: u64,
108 pub corrected_duration: u64,
112 pub start_relative_to_parent: i64,
114 pub end_relative_to_parent: i64,
116 pub args: Vec<(String, String)>,
118 pub is_aggregated: bool,
120 pub count: Option<u64>,
122 pub total_cpu_duration: Option<u64>,
124 pub avg_cpu_duration: Option<u64>,
126 pub total_corrected_duration: Option<u64>,
128 pub avg_corrected_duration: Option<u64>,
130 pub first_span_id: Option<String>,
132 pub memory_samples: Vec<(i64, u64, u8)>,
145}
146
147pub struct QueryResult {
149 pub spans: Vec<SpanInfo>,
150 pub page: usize,
151 pub total_pages: usize,
152 pub total_count: usize,
153}
154
155fn paginate<T>(items: Vec<T>, page: usize) -> (Vec<T>, usize, usize, usize) {
157 let total_count = items.len();
158 let total_pages = total_count.div_ceil(PAGE_SIZE).max(1);
159 let page = page.clamp(1, total_pages);
160 let start = (page - 1) * PAGE_SIZE;
161 let page_items = items.into_iter().skip(start).take(PAGE_SIZE).collect();
162 (page_items, page, total_pages, total_count)
163}
164
165fn format_span_name(cat: &str, title: &str) -> String {
166 if cat.is_empty() {
167 title.to_string()
168 } else {
169 format!("{cat} {title}")
170 }
171}
172
173fn build_span_id(parent: Option<&str>, leaf: &str) -> String {
175 match parent {
176 Some(p) => format!("{p}-{leaf}"),
177 None => leaf.to_string(),
178 }
179}
180
181pub fn query_spans(store: &Arc<StoreContainer>, options: QueryOptions) -> QueryResult {
186 let deadline = Instant::now() + Duration::from_secs(10);
188 loop {
189 {
190 let guard = store.read();
191 if guard.spans.len() > 1 {
193 break;
194 }
195 }
196 if Instant::now() >= deadline {
197 break;
198 }
199 thread::sleep(Duration::from_millis(50));
200 }
201
202 let store_guard = store.read();
203 let store_ref = &*store_guard;
204
205 let parent_span: Option<SpanRef<'_>> = if let Some(ref parent_id) = options.parent {
207 resolve_span_by_id(store_ref, parent_id)
208 } else {
209 None
210 };
211
212 let parent_start = parent_span.as_ref().map(|s| *s.start()).unwrap_or_default();
213
214 if options.aggregated {
215 let graph_children: Vec<_> = if let Some(ref parent) = parent_span {
218 parent
222 .graph()
223 .filter_map(|event| match event {
224 SpanGraphEventRef::Child { graph } => Some(graph),
225 SpanGraphEventRef::SelfTime { .. } => None,
226 })
227 .collect()
228 } else {
229 store_ref
231 .root_span()
232 .graph()
233 .filter_map(|event| match event {
234 SpanGraphEventRef::Child { graph } => Some(graph),
235 SpanGraphEventRef::SelfTime { .. } => None,
236 })
237 .collect()
238 };
239
240 let mut filtered: Vec<_> = if let Some(ref query) = options.search {
242 graph_children
243 .into_iter()
244 .filter(|g| {
245 let (cat, title) = g.nice_name();
246 cat.contains(query.as_str()) || title.contains(query.as_str())
247 })
248 .collect()
249 } else {
250 graph_children
251 };
252
253 match options.sort {
255 SortMode::Value => {
256 filtered.sort_by(|a, b| {
257 b.corrected_total_time()
258 .cmp(&a.corrected_total_time())
259 .then_with(|| b.total_time().cmp(&a.total_time()))
260 });
261 }
262 SortMode::Name => {
263 filtered.sort_by(|a, b| {
264 let (a_cat, a_title) = a.nice_name();
265 let (b_cat, b_title) = b.nice_name();
266 a_title.cmp(b_title).then_with(|| a_cat.cmp(b_cat))
267 });
268 }
269 SortMode::ExecutionOrder => {}
270 }
271
272 let (page_items, page, total_pages, total_count) = paginate(filtered, options.page);
273
274 let spans = page_items
275 .into_iter()
276 .map(|graph| {
277 let first = graph.first_span();
278 let (cat, title) = graph.nice_name();
279 let name = format_span_name(cat, title);
280 let count = graph.count() as u64;
281 let total_cpu = *graph.total_time();
282 let total_corrected = *graph.corrected_total_time();
283 let avg_cpu = total_cpu.checked_div(count).unwrap_or(0);
284 let avg_corrected = total_corrected.checked_div(count).unwrap_or(0);
285
286 let first_index = first.index;
291 let graph_id = build_span_id(options.parent.as_deref(), &format!("a{first_index}"));
292
293 let span_start = *first.start();
295 let span_end = *first.end();
296 let rel_start = (span_start as i64) - (parent_start as i64);
297 let rel_end = (span_end as i64) - (parent_start as i64);
298
299 let first_start_ticks = *first.start();
300 let memory_samples: Vec<(i64, u64, u8)> = store_ref
301 .memory_samples_for_range_with_ts(first.start(), first.end())
302 .into_iter()
303 .map(|(ts, mem, pressure)| {
304 ((*ts as i64) - (first_start_ticks as i64), mem, pressure)
305 })
306 .collect();
307
308 SpanInfo {
309 id: graph_id,
310 name,
311 cpu_duration: *first.total_time(),
312 corrected_duration: *first.corrected_total_time(),
313 start_relative_to_parent: rel_start,
314 end_relative_to_parent: rel_end,
315 args: first
316 .args()
317 .map(|(k, v)| (k.to_string(), v.to_string()))
318 .collect(),
319 is_aggregated: count > 1,
320 count: Some(count),
321 total_cpu_duration: Some(total_cpu),
322 avg_cpu_duration: Some(avg_cpu),
323 total_corrected_duration: Some(total_corrected),
324 avg_corrected_duration: Some(avg_corrected),
325 first_span_id: Some(first_index.to_string()),
326 memory_samples,
327 }
328 })
329 .collect();
330
331 QueryResult {
332 spans,
333 page,
334 total_pages,
335 total_count,
336 }
337 } else {
338 let raw_children: Vec<SpanRef<'_>> = if let Some(ref parent) = parent_span {
340 parent.children().collect()
341 } else {
342 store_ref.root_spans().collect()
343 };
344
345 let mut filtered: Vec<_> = if let Some(ref query) = options.search {
347 if let Some(ref parent) = parent_span {
348 parent.search(query).collect()
349 } else {
350 store_ref.root_span().search(query).collect()
351 }
352 } else {
353 raw_children
354 };
355
356 match options.sort {
358 SortMode::Value => {
359 filtered.sort_by(|a, b| {
360 b.corrected_total_time()
361 .cmp(&a.corrected_total_time())
362 .then_with(|| b.total_time().cmp(&a.total_time()))
363 });
364 }
365 SortMode::Name => {
366 filtered.sort_by(|a, b| {
367 let (a_cat, a_title) = a.nice_name();
368 let (b_cat, b_title) = b.nice_name();
369 a_title.cmp(b_title).then_with(|| a_cat.cmp(b_cat))
370 });
371 }
372 SortMode::ExecutionOrder => {}
373 }
374
375 let (page_items, page, total_pages, total_count) = paginate(filtered, options.page);
376
377 let spans = page_items
378 .into_iter()
379 .map(|span| {
380 let (cat, title) = span.nice_name();
381 let name = format_span_name(cat, title);
382 let span_start = *span.start();
383 let span_end = *span.end();
384 let rel_start = (span_start as i64) - (parent_start as i64);
385 let rel_end = (span_end as i64) - (parent_start as i64);
386
387 let raw_span_start = span_start;
388 let memory_samples: Vec<(i64, u64, u8)> = store_ref
389 .memory_samples_for_range_with_ts(span.start(), span.end())
390 .into_iter()
391 .map(|(ts, mem, pressure)| {
392 ((*ts as i64) - (raw_span_start as i64), mem, pressure)
393 })
394 .collect();
395
396 SpanInfo {
397 id: build_span_id(options.parent.as_deref(), &span.index.to_string()),
398 name,
399 cpu_duration: *span.total_time(),
400 corrected_duration: *span.corrected_total_time(),
401 start_relative_to_parent: rel_start,
402 end_relative_to_parent: rel_end,
403 args: span
404 .args()
405 .map(|(k, v)| (k.to_string(), v.to_string()))
406 .collect(),
407 is_aggregated: false,
408 count: None,
409 total_cpu_duration: None,
410 avg_cpu_duration: None,
411 total_corrected_duration: None,
412 avg_corrected_duration: None,
413 first_span_id: None,
414 memory_samples,
415 }
416 })
417 .collect();
418
419 QueryResult {
420 spans,
421 page,
422 total_pages,
423 total_count,
424 }
425 }
426}
427
428fn resolve_span_by_id<'a>(store: &'a store::Store, id: &str) -> Option<SpanRef<'a>> {
438 let last = id.split('-').next_back().unwrap_or(id);
440 let index_str = last.strip_prefix('a').unwrap_or(last);
442 let index: usize = index_str.parse().ok()?;
443 store.spans.get(index).map(|s| SpanRef {
444 span: s,
445 store,
446 index,
447 })
448}