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}
133
134pub struct QueryResult {
136 pub spans: Vec<SpanInfo>,
137 pub page: usize,
138 pub total_pages: usize,
139 pub total_count: usize,
140}
141
142fn paginate<T>(items: Vec<T>, page: usize) -> (Vec<T>, usize, usize, usize) {
144 let total_count = items.len();
145 let total_pages = total_count.div_ceil(PAGE_SIZE).max(1);
146 let page = page.clamp(1, total_pages);
147 let start = (page - 1) * PAGE_SIZE;
148 let page_items = items.into_iter().skip(start).take(PAGE_SIZE).collect();
149 (page_items, page, total_pages, total_count)
150}
151
152fn format_span_name(cat: &str, title: &str) -> String {
153 if cat.is_empty() {
154 title.to_string()
155 } else {
156 format!("{cat} {title}")
157 }
158}
159
160fn build_span_id(parent: Option<&str>, leaf: &str) -> String {
162 match parent {
163 Some(p) => format!("{p}-{leaf}"),
164 None => leaf.to_string(),
165 }
166}
167
168pub fn query_spans(store: &Arc<StoreContainer>, options: QueryOptions) -> QueryResult {
173 let deadline = Instant::now() + Duration::from_secs(10);
175 loop {
176 {
177 let guard = store.read();
178 if guard.spans.len() > 1 {
180 break;
181 }
182 }
183 if Instant::now() >= deadline {
184 break;
185 }
186 thread::sleep(Duration::from_millis(50));
187 }
188
189 let store_guard = store.read();
190 let store_ref = &*store_guard;
191
192 let parent_span: Option<SpanRef<'_>> = if let Some(ref parent_id) = options.parent {
194 resolve_span_by_id(store_ref, parent_id)
195 } else {
196 None
197 };
198
199 let parent_start = parent_span.as_ref().map(|s| *s.start()).unwrap_or_default();
200
201 if options.aggregated {
202 let graph_children: Vec<_> = if let Some(ref parent) = parent_span {
205 parent
209 .graph()
210 .filter_map(|event| match event {
211 SpanGraphEventRef::Child { graph } => Some(graph),
212 SpanGraphEventRef::SelfTime { .. } => None,
213 })
214 .collect()
215 } else {
216 store_ref
218 .root_span()
219 .graph()
220 .filter_map(|event| match event {
221 SpanGraphEventRef::Child { graph } => Some(graph),
222 SpanGraphEventRef::SelfTime { .. } => None,
223 })
224 .collect()
225 };
226
227 let mut filtered: Vec<_> = if let Some(ref query) = options.search {
229 graph_children
230 .into_iter()
231 .filter(|g| {
232 let (cat, title) = g.nice_name();
233 cat.contains(query.as_str()) || title.contains(query.as_str())
234 })
235 .collect()
236 } else {
237 graph_children
238 };
239
240 match options.sort {
242 SortMode::Value => {
243 filtered.sort_by(|a, b| {
244 b.corrected_total_time()
245 .cmp(&a.corrected_total_time())
246 .then_with(|| b.total_time().cmp(&a.total_time()))
247 });
248 }
249 SortMode::Name => {
250 filtered.sort_by(|a, b| {
251 let (a_cat, a_title) = a.nice_name();
252 let (b_cat, b_title) = b.nice_name();
253 a_title.cmp(b_title).then_with(|| a_cat.cmp(b_cat))
254 });
255 }
256 SortMode::ExecutionOrder => {}
257 }
258
259 let (page_items, page, total_pages, total_count) = paginate(filtered, options.page);
260
261 let spans = page_items
262 .into_iter()
263 .map(|graph| {
264 let first = graph.first_span();
265 let (cat, title) = graph.nice_name();
266 let name = format_span_name(cat, title);
267 let count = graph.count() as u64;
268 let total_cpu = *graph.total_time();
269 let total_corrected = *graph.corrected_total_time();
270 let avg_cpu = total_cpu.checked_div(count).unwrap_or(0);
271 let avg_corrected = total_corrected.checked_div(count).unwrap_or(0);
272
273 let first_index = first.index;
278 let graph_id = build_span_id(options.parent.as_deref(), &format!("a{first_index}"));
279
280 let span_start = *first.start();
282 let span_end = *first.end();
283 let rel_start = (span_start as i64) - (parent_start as i64);
284 let rel_end = (span_end as i64) - (parent_start as i64);
285
286 SpanInfo {
287 id: graph_id,
288 name,
289 cpu_duration: *first.total_time(),
290 corrected_duration: *first.corrected_total_time(),
291 start_relative_to_parent: rel_start,
292 end_relative_to_parent: rel_end,
293 args: first
294 .args()
295 .map(|(k, v)| (k.to_string(), v.to_string()))
296 .collect(),
297 is_aggregated: count > 1,
298 count: Some(count),
299 total_cpu_duration: Some(total_cpu),
300 avg_cpu_duration: Some(avg_cpu),
301 total_corrected_duration: Some(total_corrected),
302 avg_corrected_duration: Some(avg_corrected),
303 first_span_id: Some(first_index.to_string()),
304 }
305 })
306 .collect();
307
308 QueryResult {
309 spans,
310 page,
311 total_pages,
312 total_count,
313 }
314 } else {
315 let raw_children: Vec<SpanRef<'_>> = if let Some(ref parent) = parent_span {
317 parent.children().collect()
318 } else {
319 store_ref.root_spans().collect()
320 };
321
322 let mut filtered: Vec<_> = if let Some(ref query) = options.search {
324 if let Some(ref parent) = parent_span {
325 parent.search(query).collect()
326 } else {
327 store_ref.root_span().search(query).collect()
328 }
329 } else {
330 raw_children
331 };
332
333 match options.sort {
335 SortMode::Value => {
336 filtered.sort_by(|a, b| {
337 b.corrected_total_time()
338 .cmp(&a.corrected_total_time())
339 .then_with(|| b.total_time().cmp(&a.total_time()))
340 });
341 }
342 SortMode::Name => {
343 filtered.sort_by(|a, b| {
344 let (a_cat, a_title) = a.nice_name();
345 let (b_cat, b_title) = b.nice_name();
346 a_title.cmp(b_title).then_with(|| a_cat.cmp(b_cat))
347 });
348 }
349 SortMode::ExecutionOrder => {}
350 }
351
352 let (page_items, page, total_pages, total_count) = paginate(filtered, options.page);
353
354 let spans = page_items
355 .into_iter()
356 .map(|span| {
357 let (cat, title) = span.nice_name();
358 let name = format_span_name(cat, title);
359 let span_start = *span.start();
360 let span_end = *span.end();
361 let rel_start = (span_start as i64) - (parent_start as i64);
362 let rel_end = (span_end as i64) - (parent_start as i64);
363
364 SpanInfo {
365 id: build_span_id(options.parent.as_deref(), &span.index.to_string()),
366 name,
367 cpu_duration: *span.total_time(),
368 corrected_duration: *span.corrected_total_time(),
369 start_relative_to_parent: rel_start,
370 end_relative_to_parent: rel_end,
371 args: span
372 .args()
373 .map(|(k, v)| (k.to_string(), v.to_string()))
374 .collect(),
375 is_aggregated: false,
376 count: None,
377 total_cpu_duration: None,
378 avg_cpu_duration: None,
379 total_corrected_duration: None,
380 avg_corrected_duration: None,
381 first_span_id: None,
382 }
383 })
384 .collect();
385
386 QueryResult {
387 spans,
388 page,
389 total_pages,
390 total_count,
391 }
392 }
393}
394
395fn resolve_span_by_id<'a>(store: &'a store::Store, id: &str) -> Option<SpanRef<'a>> {
405 let last = id.split('-').next_back().unwrap_or(id);
407 let index_str = last.strip_prefix('a').unwrap_or(last);
409 let index: usize = index_str.parse().ok()?;
410 store.spans.get(index).map(|s| SpanRef {
411 span: s,
412 store,
413 index,
414 })
415}