next_napi_bindings/turbo_trace_server.rs
1use std::{path::PathBuf, sync::Arc};
2
3use napi_derive::napi;
4use turbopack_trace_server::{
5 QueryOptions, SortMode, query_spans, start_turbopack_trace_server,
6 store_container::StoreContainer,
7};
8
9/// An opaque handle to a running trace server instance.
10/// Holds a reference to the shared store so that `query_trace_spans` can
11/// query it without blocking Node.js with the WebSocket server loop.
12#[napi]
13pub struct TraceServerHandle {
14 store: Arc<StoreContainer>,
15}
16
17/// Options for `query_trace_spans`.
18#[napi(object)]
19pub struct TraceQueryOptions {
20 /// Optional parent span ID (as returned by a previous query).
21 /// Omit or set to `null`/`undefined` for root-level spans.
22 pub parent: Option<String>,
23 /// When `true` (default), aggregate child spans with the same name.
24 pub aggregated: Option<bool>,
25 /// Sort mode: `"value"` for duration descending, `"name"` for alphabetical.
26 /// Omit for execution order (no sorting).
27 pub sort: Option<String>,
28 /// Optional substring search query applied to span name/category.
29 pub search: Option<String>,
30 /// 1-based page number. Default `1`.
31 pub page: Option<u32>,
32}
33
34/// Information about a single span or aggregated span group.
35#[napi(object)]
36pub struct TraceSpanInfo {
37 /// Span ID. Pass this as `parent` in a follow-up call to get children.
38 pub id: String,
39 /// Display name of the span.
40 pub name: String,
41 /// Raw CPU total time in internal ticks (100 ticks = 1 µs).
42 pub cpu_duration: i64,
43 /// Concurrency-corrected total time in internal ticks (100 ticks = 1 µs).
44 pub corrected_duration: i64,
45 /// Start time relative to parent start, in internal ticks.
46 pub start_relative_to_parent: i64,
47 /// End time relative to parent start, in internal ticks.
48 pub end_relative_to_parent: i64,
49 /// Key-value attributes attached to the span.
50 pub args: Vec<Vec<String>>,
51 /// True if this entry represents an aggregated group of spans.
52 pub is_aggregated: bool,
53 /// Number of spans in this aggregated group (only set when `is_aggregated`).
54 pub count: Option<i64>,
55 /// Sum of CPU duration across all spans in the group.
56 pub total_cpu_duration: Option<i64>,
57 /// Average CPU duration across spans in the group.
58 pub avg_cpu_duration: Option<i64>,
59 /// Sum of corrected duration across all spans in the group.
60 pub total_corrected_duration: Option<i64>,
61 /// Average corrected duration across spans in the group.
62 pub avg_corrected_duration: Option<i64>,
63 /// Raw span ID for aggregated groups (the index of the first span).
64 pub first_span_id: Option<String>,
65 /// TurboMalloc memory-usage samples recorded while this span
66 /// (or its example span, for aggregated groups) was live.
67 ///
68 /// Each entry is `[ts_offset_from_span_start_in_ticks, bytes, pressure]`,
69 /// where `pressure` is the memory-pressure byte (0 = no pressure, higher
70 /// = more pressure). `100 ticks = 1 µs`. The offset is always `>= 0` and
71 /// `<= span_duration`. Capped and downsampled by the store.
72 pub memory_samples: Vec<Vec<i64>>,
73}
74
75/// The result of a `query_trace_spans` call.
76#[napi(object)]
77pub struct TraceQueryResult {
78 pub spans: Vec<TraceSpanInfo>,
79 /// Current page (1-based).
80 pub page: u32,
81 /// Total number of pages available.
82 pub total_pages: u32,
83 /// Total number of matching spans across all pages.
84 pub total_count: u32,
85}
86
87/// Starts the turbopack trace server on a background thread and returns a
88/// handle immediately (non-blocking). The WebSocket server will be available
89/// at `ws://127.0.0.1:<port>` (default port 5747).
90#[napi]
91pub fn start_turbopack_trace_server_handle(path: String, port: Option<u16>) -> TraceServerHandle {
92 let store = start_turbopack_trace_server(PathBuf::from(path), port);
93 TraceServerHandle { store }
94}
95
96/// Query spans from the trace store held by a `TraceServerHandle`.
97#[napi]
98pub fn query_trace_spans(
99 handle: &TraceServerHandle,
100 options: TraceQueryOptions,
101) -> TraceQueryResult {
102 let result = query_spans(
103 &handle.store,
104 QueryOptions {
105 parent: options.parent,
106 aggregated: options.aggregated.unwrap_or(true),
107 sort: match options.sort.as_deref() {
108 Some("value") => SortMode::Value,
109 Some("name") => SortMode::Name,
110 _ => SortMode::ExecutionOrder,
111 },
112 search: options.search,
113 page: options.page.unwrap_or(1) as usize,
114 },
115 );
116
117 TraceQueryResult {
118 spans: result
119 .spans
120 .into_iter()
121 .map(|s| TraceSpanInfo {
122 id: s.id,
123 name: s.name,
124 cpu_duration: s.cpu_duration as i64,
125 corrected_duration: s.corrected_duration as i64,
126 start_relative_to_parent: s.start_relative_to_parent,
127 end_relative_to_parent: s.end_relative_to_parent,
128 args: s.args.into_iter().map(|(k, v)| vec![k, v]).collect(),
129 is_aggregated: s.is_aggregated,
130 count: s.count.map(|c| c as i64),
131 total_cpu_duration: s.total_cpu_duration.map(|v| v as i64),
132 avg_cpu_duration: s.avg_cpu_duration.map(|v| v as i64),
133 total_corrected_duration: s.total_corrected_duration.map(|v| v as i64),
134 avg_corrected_duration: s.avg_corrected_duration.map(|v| v as i64),
135 first_span_id: s.first_span_id,
136 memory_samples: s
137 .memory_samples
138 .into_iter()
139 .map(|(ts, mem, pressure)| vec![ts, mem as i64, pressure as i64])
140 .collect(),
141 })
142 .collect(),
143 page: result.page as u32,
144 total_pages: result.total_pages as u32,
145 total_count: result.total_count as u32,
146 }
147}