Trace Event Format summary CLI
13 kB
332 lines
1(*---------------------------------------------------------------------------
2 Copyright (c) 2026 Thomas Gazagnaire. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6(** Trace Event Format (Catapult) JSON codecs.
7
8 The
9 {{:https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU}
10 Trace Event Format} is the JSON trace format consumed by
11 [chrome://tracing], {{:https://ui.perfetto.dev}Perfetto} and the Catapult
12 tools. This library provides typed values and {!Json.codec}s for it:
13
14 - {!Event}, one trace event (a {!Phase.t} plus the common members);
15 - {!type-t}, a whole trace in either container format: the JSON array format
16 (a plain list of events, allowing an unterminated tail) or the JSON object
17 format ([traceEvents] plus stack frames, samples and metadata);
18 - {!Writer}, a streaming array-format emitter for trace producers.
19
20 Produce and consume a trace:
21
22 {[
23 let trace =
24 Catapult.v
25 [
26 Catapult.Event.process_name ~pid:1 "compiler";
27 Catapult.Event.complete ~pid:1 ~tid:1 ~name:"parse" ~ts:10. ~dur:200.
28 ();
29 ]
30
31 let s = Catapult.to_string trace
32 let trace' = Catapult.of_string_exn s
33 let () = assert (Catapult.equal trace trace')
34 ]} *)
35
36(** {1:events Events} *)
37
38module Phase = Phase
39(** Event phases ([ph] member). *)
40
41module Event = Event
42(** Trace events. *)
43
44(** {1:traces Traces} *)
45
46module Frame = Trace.Frame
47(** Stack frames ([stackFrames] dictionary values). *)
48
49module Sample = Trace.Sample
50(** Sampling profiler samples, the elements of the {!field-samples} array. *)
51
52type time_unit = Trace.time_unit
53(** Display time units for the trace viewer ([displayTimeUnit] member). *)
54
55type t = Trace.t = {
56 events : Event.t list; (** The [traceEvents] member. *)
57 display_time_unit : time_unit option;
58 system_trace_events : string option;
59 (** Linux ftrace or Windows ETW data, raw. *)
60 power_trace_as_string : string option; (** BattOr power data, raw. *)
61 controller_trace_data_key : string option;
62 (** Metadata key identifying the controller tracing agent's data. *)
63 stack_frames : (string * Frame.t) list;
64 (** The [stackFrames] dictionary, in source order. *)
65 samples : Sample.t list; (** The [samples] array. *)
66 metadata : (string * Json.t) list;
67 (** Any other object-format member, in source order. *)
68}
69(** The type for traces. *)
70
71val v :
72 ?display_time_unit:time_unit ->
73 ?system_trace_events:string ->
74 ?power_trace_as_string:string ->
75 ?controller_trace_data_key:string ->
76 ?stack_frames:(string * Frame.t) list ->
77 ?samples:Sample.t list ->
78 ?metadata:(string * Json.t) list ->
79 Event.t list ->
80 t
81(** [v events] is a trace of [events]. All extras default to absent or empty. *)
82
83val events_only : t -> bool
84(** [events_only t] is [true] iff [t] carries nothing but events, i.e. it can be
85 encoded in the array format without loss. *)
86
87val equal : t -> t -> bool
88(** [equal a b] compares the JSON encodings of [a] and [b]. *)
89
90val pp : t Fmt.t
91(** [pp] prints the JSON encoding of the trace. *)
92
93(** {1:rewrite Trace composition and rewriting} *)
94
95val map_events : (Event.t -> Event.t) -> t -> t
96(** [map_events f t] applies [f] to every event in [t]. *)
97
98val map_samples : (Sample.t -> Sample.t) -> t -> t
99(** [map_samples f t] applies [f] to every sample in [t]. *)
100
101val shift_time : float -> t -> t
102(** [shift_time dt t] adds [dt] microseconds to every event timestamp, thread
103 timestamp and sample timestamp in [t]. Durations are unchanged. *)
104
105val rewrite_pid : (int -> int) -> t -> t
106(** [rewrite_pid f t] rewrites every event [pid] in [t] with [f]. Events without
107 a [pid] are left unchanged. *)
108
109val rewrite_tid : (int -> int) -> t -> t
110(** [rewrite_tid f t] rewrites every event and sample [tid] in [t] with [f].
111 Values without a [tid] are left unchanged. *)
112
113val set_pid : int -> t -> t
114(** [set_pid pid t] sets every event with a [pid] to [pid]. Events without a
115 [pid] are left unchanged. *)
116
117val set_tid : int -> t -> t
118(** [set_tid tid t] sets every event and sample with a [tid] to [tid]. Values
119 without a [tid] are left unchanged. *)
120
121val add_process_metadata : pid:int -> name:string -> t -> t
122(** [add_process_metadata ~pid ~name t] prepends a Catapult [process_name]
123 metadata event for [pid]. *)
124
125val add_thread_metadata : pid:int -> tid:int -> name:string -> t -> t
126(** [add_thread_metadata ~pid ~tid ~name t] prepends a Catapult [thread_name]
127 metadata event for [tid] in [pid]. *)
128
129val merge : ?time_offset:float -> t list -> t
130(** [merge ?time_offset traces] is one Catapult trace containing the events and
131 object-format extras of [traces], in order.
132
133 When [time_offset] is provided it is added to event and sample timestamps in
134 every input before concatenation. This is intentionally a plain offset; use
135 {!shift_time} on individual traces first when each input needs a different
136 alignment.
137
138 Stack-frame dictionaries are namespaced by input index and sample [sf]
139 references are rewritten to the new keys, so sampling data from different
140 producers cannot collide. Event-level [sf]/[stack] members live in
141 {!Event.t.extra} and are not rewritten; merge traces using them only when
142 their frame ids already agree. Colliding [pid]s are not disambiguated
143 either: apply {!rewrite_pid} per input first. Display time unit and
144 controller data keep the first value present. Raw system/power trace strings
145 are concatenated with a newline separator, and free-form metadata members
146 are appended in input order. *)
147
148(** {1:codecs Codecs}
149
150 Pass these to the {!Json} entry points for uses not covered by the functions
151 below, e.g. decoding with source locations or embedding a trace inside a
152 larger JSON document. *)
153
154val codec : t Json.codec
155(** JSON codec for traces. Decodes both container formats; encodes the array
156 format when {!events_only} holds and the object format otherwise. *)
157
158val array_codec : t Json.codec
159(** [array_codec] is jSON codec for the array container format. Encoding raises
160 [Invalid_argument] when {!events_only} is [false]. *)
161
162val object_codec : t Json.codec
163(** JSON codec for the object container format. *)
164
165(** {1:slices Slices} *)
166
167module Slice = Slice
168(** Events paired into time intervals, the rows a trace viewer draws. *)
169
170(** {1:errors Errors} *)
171
172module Error = Json.Error
173(** Decode errors, shared with {!Json}. *)
174
175exception Error of Error.t
176(** Alias for the {!Json} error exception, raised by [_exn] functions. *)
177
178(** {1:io Byte-stream decoding and encoding} *)
179
180val of_string :
181 ?meta:Json.meta -> ?file:Json.fpath -> string -> (t, Error.t) result
182(** [of_string s] decodes a trace from [s] in either container format. An
183 array-format input may lack the closing bracket (and have a trailing comma),
184 as written by producers that crashed mid-trace: the tail is repaired and
185 parsing retried. A complete trailing event is required; a partially written
186 final event is an error. *)
187
188val of_string_exn : ?meta:Json.meta -> ?file:Json.fpath -> string -> t
189(** [of_string_exn] is like {!of_string} but raises {!exception-Error}. *)
190
191val of_reader :
192 ?meta:Json.meta ->
193 ?file:Json.fpath ->
194 Bytesrw.Bytes.Reader.t ->
195 (t, Error.t) result
196(** [of_reader r] decodes a trace from [r]. Unlike {!of_string} the input is not
197 buffered, so the array-format tail repair is not available: the input must
198 be well-formed JSON. *)
199
200val of_reader_exn :
201 ?meta:Json.meta -> ?file:Json.fpath -> Bytesrw.Bytes.Reader.t -> t
202(** [of_reader_exn] is like {!of_reader} but raises {!exception-Error}. *)
203
204val fold_array_events :
205 ?meta:Json.meta ->
206 ?file:Json.fpath ->
207 init:'a ->
208 f:('a -> Event.t -> 'a) ->
209 Bytesrw.Bytes.Reader.t ->
210 ('a, Error.t) result
211(** [fold_array_events ~init ~f r] folds [f] over the events of an array-format
212 trace read from [r], one event at a time. [init] is the accumulator for an
213 empty trace and [f acc e] combines the running accumulator with the next
214 event [e].
215
216 Only the array container format is read. Events are streamed straight from
217 [r] through {!Event.codec} and never collected, so a trace far larger than
218 memory can be consumed in a single pass by an [f] that retains only what it
219 needs; folding into a list reconstructs {!of_reader}'s events.
220
221 A trace truncated by a crashed or stopped producer (an unclosed array, a
222 trailing comma, or a half-written final event) is folded up to the
223 truncation: decoding stops at the first incomplete event and the accumulator
224 over the complete events before it is returned. An input whose root is not a
225 JSON array, or that yields no complete event, is an error. *)
226
227val fold_array_events_exn :
228 ?meta:Json.meta ->
229 ?file:Json.fpath ->
230 init:'a ->
231 f:('a -> Event.t -> 'a) ->
232 Bytesrw.Bytes.Reader.t ->
233 'a
234(** [fold_array_events_exn] is like {!fold_array_events} but raises
235 {!exception-Error}. *)
236
237val to_string : ?indent:int -> ?format:[ `Array | `Object ] -> t -> string
238(** [to_string t] encodes [t]. [indent] is the standard pretty-printing knob:
239 omitted means compact, [~indent:n] indents nested structures by [n] spaces
240 per level. [format] forces a container format; by default the array format
241 is used when {!events_only} holds and the object format otherwise. Forcing
242 [`Array] raises [Invalid_argument] when the trace carries non-event data. *)
243
244val to_writer :
245 ?indent:int ->
246 ?format:[ `Array | `Object ] ->
247 t ->
248 eod:bool ->
249 Bytesrw.Bytes.Writer.t ->
250 unit
251(** [to_writer t ~eod w] is like {!to_string} but writes on [w]. [eod] indicates
252 whether the end of data slice is written on [w]. *)
253
254(** {1:protobuf Protocol Buffers}
255
256 A binary encoding of the same {!type-t}: the typed event members are native
257 protobuf fields and the open-ended [args], [extra] and [metadata] values are
258 carried as their JSON text. It round-trips a trace unchanged and decodes
259 without the JSON tokenizer, so it is markedly cheaper to read and write than
260 the JSON forms above. *)
261
262val pb_codec : t Protobuf.t
263(** Protobuf codec for traces, for uses not covered by the functions below. *)
264
265val to_protobuf : t -> string
266(** [to_protobuf t] encodes [t] to its protobuf wire form. *)
267
268val to_protobuf_writer : t -> Bytesrw.Bytes.Writer.t -> unit
269(** [to_protobuf_writer t w] is like {!to_protobuf} but writes on [w]. *)
270
271val of_protobuf : string -> (t, Protobuf.Error.t) result
272(** [of_protobuf s] decodes a trace from its protobuf wire form. *)
273
274val of_protobuf_exn : string -> t
275(** [of_protobuf_exn] is like {!of_protobuf} but raises on a decode error. *)
276
277val of_protobuf_reader : Bytesrw.Bytes.Reader.t -> (t, Protobuf.Error.t) result
278(** [of_protobuf_reader r] decodes a trace from the protobuf bytes of [r]. *)
279
280val of_protobuf_reader_exn : Bytesrw.Bytes.Reader.t -> t
281(** [of_protobuf_reader_exn] is like {!of_protobuf_reader} but raises. *)
282
283val fold_protobuf_events :
284 init:'a -> f:('a -> Event.t -> 'a) -> string -> ('a, Protobuf.Error.t) result
285(** [fold_protobuf_events ~init ~f s] folds [f] over the events of the
286 protobuf-encoded trace [s], one event at a time, without materialising the
287 event list -- the binary counterpart to {!fold_array_events}. A trace
288 truncated by a crashed producer is folded up to the truncation. *)
289
290val fold_protobuf_events_reader :
291 init:'a ->
292 f:('a -> Event.t -> 'a) ->
293 Bytesrw.Bytes.Reader.t ->
294 ('a, Protobuf.Error.t) result
295(** [fold_protobuf_events_reader] is {!fold_protobuf_events} reading the
296 protobuf bytes from a reader. *)
297
298(** Streaming protobuf emitter for trace producers, the binary counterpart to
299 {!Writer}. Each {!event} is written as it arrives, so a producer never holds
300 the whole trace. *)
301module Pb_writer : sig
302 type t
303 (** A streaming protobuf trace writer. *)
304
305 val of_writer : Bytesrw.Bytes.Writer.t -> t
306 (** [of_writer w] is a writer emitting onto [w]. *)
307
308 val event : t -> Event.t -> unit
309 (** [event t e] appends [e] to the trace. Raises [Invalid_argument] once
310 {!close} has been called. *)
311
312 val close : ?eod:bool -> t -> unit
313 (** [close t] finishes the trace. [eod] (default [true]) writes the end of
314 data slice on the underlying writer. Idempotent. *)
315end
316
317(** {1:runtime Runtime over {!Json.t}} *)
318
319val decode : Json.t -> (t, Error.t) result
320(** [decode j] decodes a trace from a generic JSON value. *)
321
322val decode_exn : Json.t -> t
323(** [decode_exn] is like {!decode} but raises {!exception-Error}. *)
324
325val encode : ?format:[ `Array | `Object ] -> t -> Json.t
326(** [encode t] is [t] as a generic JSON value. [format] behaves as in
327 {!to_string}. *)
328
329(** {1:writer Streaming production} *)
330
331module Writer = Writer
332(** Streaming array-format emitter for trace producers. *)