Skip to main content

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}