xref: /aosp_15_r20/external/perfetto/docs/reference/synthetic-track-event.md (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1# Writing TrackEvent Protos Synthetically
2
3This page acts as a reference guide to synthetically generate TrackEvent,
4Perfetto's native protobuf based tracing format. This allows using Perfetto's
5analysis and visualzation without using collecting traces using the Perfetto
6SDK.
7
8TrackEvent protos can be manually written using the
9[official protobuf library](https://protobuf.dev/reference/) or any other
10protobuf-compatible library. To be language-agnostic, the rest of this page will
11show examples using the
12[text format](https://protobuf.dev/reference/protobuf/textformat-spec/)
13representation of protobufs.
14
15The root container of the protobuf-based traces is the
16[Trace](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/trace.proto)
17message which itself is simply a repeated field of
18[TracePacket](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/protos/perfetto/trace/trace_packet.proto)
19messages.
20
21## Thread-scoped (sync) slices
22
23NOTE: in the legacy JSON tracing format, this section correspond to B/E/I/X
24events with the associated M (metadata) events.
25
26Thread scoped slices are used to trace execution of functions on a single
27thread. As only one function runs on a single thread over time, this requires
28that child slices nest perfectly inside parent slices and do not partially
29overlap.
30
31![Thread track event in UI](/docs/images/synthetic-track-event-thread.png)
32
33This is corresponds to the following protos:
34
35```
36# Emit this packet once *before* you emit the first event for this process.
37packet {
38  track_descriptor: {
39    uuid: 894893984                     # 64-bit random number.
40    process: {
41      pid: 1234                         # PID for your process.
42      process_name: "My process name"
43    }
44  }
45}
46
47# Emit this packet once *before* you emit the first event for this thread.
48packet {
49  track_descriptor: {
50    uuid: 49083589894                   # 64-bit random number.
51    thread: {
52      pid: 1234                         # PID for your process.
53      tid: 5678                         # TID for your thread.
54      thread_name: "My thread name"
55    }
56  }
57}
58
59# The events for this thread.
60packet {
61  timestamp: 200
62  track_event: {
63    type: TYPE_SLICE_BEGIN
64    track_uuid: 49083589894             # Same random number from above.
65    name: "My special parent"
66  }
67  trusted_packet_sequence_id: 3903809   # Generate *once*, use throughout.
68}
69packet {
70  timestamp: 250
71  track_event: {
72    type: TYPE_SLICE_BEGIN
73    track_uuid: 49083589894
74    name: "My special child"
75  }
76  trusted_packet_sequence_id: 3903809
77}
78packet {
79  timestamp: 285
80  track_event {
81    type: TYPE_INSTANT
82    track_uuid: 49083589894
83  }
84  trusted_packet_sequence_id: 3903809
85}
86packet {
87  timestamp: 290
88  track_event: {
89    type: TYPE_SLICE_END
90    track_uuid: 49083589894
91  }
92  trusted_packet_sequence_id: 3903809
93}
94packet {
95  timestamp: 300
96  track_event: {
97    type: TYPE_SLICE_END
98    track_uuid: 49083589894
99  }
100  trusted_packet_sequence_id: 3903809
101}
102```
103
104## Process-scoped (async) slices
105
106NOTE: in the legacy JSON tracing format, this section corresponds to b/e/n
107events with the associated M (metadata) events.
108
109Process-scoped slices are useful to trace execution of a "piece of work" across
110multiple threads of a process. A process-scoped slice can start on a thread A
111and end on a thread B. Examples include work submitted to thread pools and
112coroutines.
113
114Process tracks can be named corresponding to the executor and can also have
115child slices in an identical way to thread-scoped slices. Importantly, this
116means slices on a single track must **strictly nest** inside each other without
117overlapping.
118
119As separating each track in the UI can cause a lot of clutter, the UI visually
120merges process tracks with the same name in each process. Note that this **does
121not** change the data model (e.g. in trace processor tracks remain separated) as
122this is simply a visual grouping.
123
124![Process track event in UI](/docs/images/synthetic-track-event-process.png)
125
126This is corresponds to the following protos:
127
128```
129# The first track associated with this process.
130packet {
131  track_descriptor {
132    uuid: 48948                         # 64-bit random number.
133    name: "My special track"
134    process {
135      pid: 1234                         # PID for your process
136      process_name: "My process name"
137    }
138  }
139}
140# The events for the first track.
141packet {
142  timestamp: 200
143  track_event {
144    type: TYPE_SLICE_BEGIN
145    track_uuid: 48948                   # Same random number from above.
146    name: "My special parent A"
147  }
148  trusted_packet_sequence_id: 3903809   # Generate *once*, use throughout.
149}
150packet {
151  timestamp: 250
152  track_event {
153    type: TYPE_SLICE_BEGIN
154    track_uuid: 48948
155    name: "My special child"
156  }
157  trusted_packet_sequence_id: 3903809
158}
159packet {
160  timestamp: 290
161  track_event {
162    type: TYPE_SLICE_END
163    track_uuid: 48948
164  }
165  trusted_packet_sequence_id: 3903809
166}
167packet {
168  timestamp: 300
169  track_event {
170    type: TYPE_SLICE_END
171    track_uuid: 48948
172  }
173  trusted_packet_sequence_id: 3903809
174}
175
176# The second track associated with this process. Note how we make the above
177# track the "parent" of this track: this means that this track also is
178# associated to the same process. Note further this shows as the same visual
179# track in the UI but remains separate in the trace and data model. Emitting
180# these events on a separate track is necessary because these events overlap
181# *without* nesting with the above events.
182packet {
183  track_descriptor {
184      uuid: 2390190934                  # 64-bit random number.
185      name: "My special track"
186      parent_uuid: 48948
187  }
188}
189# The events for the second track.
190packet {
191  timestamp: 230
192  track_event {
193    type: TYPE_SLICE_BEGIN
194    track_uuid: 2390190934              # Same random number from above.
195    name: "My special parent A"
196  }
197  trusted_packet_sequence_id: 3903809
198}
199packet {
200  timestamp: 260
201  track_event {
202    type: TYPE_SLICE_BEGIN
203    track_uuid: 2390190934
204    name: "My special child"
205  }
206  trusted_packet_sequence_id: 3903809
207}
208packet {
209  timestamp: 270
210  track_event {
211    type: TYPE_SLICE_END
212    track_uuid: 2390190934
213  }
214  trusted_packet_sequence_id: 3903809
215}
216packet {
217  timestamp: 295
218  track_event {
219    type: TYPE_SLICE_END
220    track_uuid: 2390190934
221  }
222  trusted_packet_sequence_id: 3903809
223}
224```
225
226## Custom-scoped slices
227
228NOTE: there is no equivalent in the JSON tracing format.
229
230As well as thread-scoped and process-scoped slices, Perfetto supports creating
231tracks which are not scoped to any OS-level concept. Moreover, these tracks can
232be recursively nested in a tree structure. This is useful to model the timeline
233of execution of GPUs, network traffic, IRQs etc.
234
235Note: in the past, modelling such slices may have been done by abusing
236processes/threads slices, due to limitations with the data model and the
237Perfetto UI. This is no longer necessary and we _strongly_ discourage continued
238use of this hack.
239
240![Process track event in UI](/docs/images/synthetic-track-event-custom-tree.png)
241
242This is corresponds to the following protos:
243
244```
245packet {
246  track_descriptor {
247    uuid: 48948                         # 64-bit random number.
248    name: "Root"
249  }
250}
251packet {
252  track_descriptor {
253    uuid: 50001                         # 64-bit random number.
254    parent_uuid: 48948                  # UUID of root track.
255    name: "Parent B"
256  }
257}
258packet {
259  track_descriptor {
260    uuid: 50000                         # 64-bit random number.
261    parent_uuid: 48948                  # UUID of root track.
262    name: "Parent A"
263  }
264}
265packet {
266  track_descriptor {
267    uuid: 60000                         # 64-bit random number.
268    parent_uuid: 50000                  # UUID of Parent A track.
269    name: "Child A1"
270  }
271}
272packet {
273  track_descriptor {
274    uuid: 60001                         # 64-bit random number.
275    parent_uuid: 50000                  # UUID of Parent A track.
276    name: "Child A2"
277  }
278}
279packet {
280  track_descriptor {
281    uuid: 70000                         # 64-bit random number.
282    parent_uuid: 50001                  # UUID of Parent B track.
283    name: "Child B1"
284  }
285}
286
287# The events for the Child A1 track.
288packet {
289  timestamp: 200
290  track_event {
291    type: TYPE_SLICE_BEGIN
292    track_uuid: 60000                   # Same random number from above.
293    name: "A1"
294  }
295  trusted_packet_sequence_id: 3903809   # Generate *once*, use throughout.
296}
297packet {
298  timestamp: 250
299  track_event {
300    type: TYPE_SLICE_END
301    track_uuid: 60000
302  }
303  trusted_packet_sequence_id: 3903809
304}
305
306# The events for the Child A2 track.
307packet {
308  timestamp: 220
309  track_event {
310    type: TYPE_SLICE_BEGIN
311    track_uuid: 60001                   # Same random number from above.
312    name: "A2"
313  }
314  trusted_packet_sequence_id: 3903809   # Generate *once*, use throughout.
315}
316packet {
317  timestamp: 240
318  track_event {
319    type: TYPE_SLICE_END
320    track_uuid: 60001
321  }
322  trusted_packet_sequence_id: 3903809
323}
324
325# The events for the Child B1 track.
326packet {
327  timestamp: 210
328  track_event {
329    type: TYPE_SLICE_BEGIN
330    track_uuid: 70000                   # Same random number from above.
331    name: "B1"
332  }
333  trusted_packet_sequence_id: 3903809   # Generate *once*, use throughout.
334}
335packet {
336  timestamp: 230
337  track_event {
338    type: TYPE_SLICE_END
339    track_uuid: 70000
340  }
341  trusted_packet_sequence_id: 3903809
342}
343```
344
345## Track sorting order
346
347NOTE: the closest equivalent to this in the JSON format is `process_sort_index`
348but the Perfetto approach is significantly more flexible.
349
350Perfetto also supports specifying of how the tracks should be visualized in the
351UI by default. This is done via the use of the `child_ordering` field which can
352be set on `TrackDescriptor`.
353
354For example, to sort the tracks lexicographically (i.e. in alphabetical order):
355
356```
357packet {
358  track_descriptor {
359    uuid: 10
360    name: "Root"
361    # Any children of the `Root` track will appear in alphabetical order. This
362    # does *not* propogate to any indirect descendants, just the direct
363    # children.
364    child_ordering: LEXICOGRAPHIC
365  }
366}
367# B will appear nested under `Root` but *after* `A` in the UI, even though it
368# appears first in the trace and has a smaller UUID.
369packet {
370  track_descriptor {
371    uuid: 11
372    parent_uuid: 10
373    name: "B"
374  }
375}
376packet {
377  track_descriptor {
378    uuid: 12
379    parent_uuid: 10
380    name: "A"
381  }
382}
383```
384
385Chronological order is also supported, this sorts the tracks with the earliest
386event first:
387
388```
389packet {
390  track_descriptor {
391    uuid: 10
392    name: "Root"
393    # Any children of the `Root` track will appear in the order based on the
394    # timestamp of the first event on the trace: earlier timestamps will appear
395    # higher in the trace. This does *not* propogate to any indirect
396    # descendants, just the direct children.
397    child_ordering: CHRONOLOGICAL
398  }
399}
400
401# B will appear before A because B's first slice starts earlier than A's first
402# slice.
403packet {
404  track_descriptor {
405    uuid: 11
406    parent_uuid: 10
407    name: "A"
408  }
409}
410packet {
411  timestamp: 220
412  track_event {
413    type: TYPE_SLICE_BEGIN
414    track_uuid: 11
415    name: "A1"
416  }
417  trusted_packet_sequence_id: 3903809
418}
419packet {
420  timestamp: 230
421  track_event {
422    type: TYPE_SLICE_END
423    track_uuid: 60000
424  }
425  trusted_packet_sequence_id: 3903809
426}
427
428packet {
429  track_descriptor {
430    uuid: 12
431    parent_uuid: 10
432    name: "B"
433  }
434}
435packet {
436  timestamp: 210
437  track_event {
438    type: TYPE_SLICE_BEGIN
439    track_uuid: 12
440    name: "B1"
441  }
442  trusted_packet_sequence_id: 3903809
443}
444packet {
445  timestamp: 240
446  track_event {
447    type: TYPE_SLICE_END
448    track_uuid: 12
449  }
450  trusted_packet_sequence_id: 3903809
451}
452```
453
454Finally, for exact control, you can use the `EXPLICIT` ordering and specify
455`sibling_order_rank` on each child track:
456
457```
458packet {
459  track_descriptor {
460    uuid: 10
461    name: "Root"
462    # Any children of the `Root` track will appear in order specified by
463    # `sibling_order_rank` exactly: any unspecified rank is treated as 0
464    # implicitly.
465    child_ordering: EXPLICIT
466  }
467}
468# C will appear first, then B then A following the order specified by
469# `sibling_order_rank`.
470packet {
471  track_descriptor {
472    uuid: 11
473    parent_uuid: 10
474    name: "B"
475    sibling_order_rank: 1
476  }
477}
478packet {
479  track_descriptor {
480    uuid: 12
481    parent_uuid: 10
482    name: "A"
483    sibling_order_rank: 100
484  }
485}
486packet {
487  track_descriptor {
488    uuid: 13
489    parent_uuid: 10
490    name: "C"
491    sibling_order_rank: -100
492  }
493}
494```
495
496NOTE: using `EXPLICIT` is strongly discouraged where there is another option.
497Other orders are significantly more efficient and also allows for trace
498processor and the UI to better understand what you want to do with those tracks.
499Moreover, it gives the flexibility for having custom visualization (e.g. Gannt
500charts for CHRONOLOGICAL view) based on the type specified.
501
502Further documentation about the sorting order is available on the protos for
503[TrackDescriptor](/docs/reference/trace-packet-proto.autogen#TrackDescriptor)
504and
505[ChildTracksOrdering](/docs/reference/trace-packet-proto.autogen#TrackDescriptor.ChildTracksOrdering).
506
507NOTE: the order specified in the trace is a treated as a hint in the UI not a
508gurantee. The UI reserves the right to change the ordering as it sees fit.
509
510## Flows
511
512NOTE: in the legacy JSON tracing format, this section correspond to s/t/f
513events.
514
515Flows allow connecting any number of slices with arrows. The semantic meaning of
516the arrow varies across different applications but most commonly it is used to
517track work passing between threads or processes: e.g. the UI thread asks a
518background thread to do some work and notify when the result is available.
519
520NOTE: a single flow _cannot_ fork ands imply represents a single stream of
521arrows from one slice to the next. See
522[this](https://source.chromium.org/chromium/chromium/src/+/main:third_party/perfetto/protos/perfetto/trace/perfetto_trace.proto;drc=ba05b783d9c29fe334a02913cf157ea1d415d37c;l=9604)
523comment for information.
524
525![TrackEvent flows in UI](/docs/images/synthetic-track-event-flow.png)
526
527```
528# The main thread of the process.
529packet {
530  track_descriptor {
531    uuid: 93094
532    thread {
533        pid: 100
534        tid: 100
535        thread_name: "Main thread"
536    }
537  }
538}
539packet {
540  timestamp: 200
541  track_event {
542    type: TYPE_SLICE_BEGIN
543    track_uuid: 93094
544    name: "Request generation"
545    flow_ids: 1055895987                  # Random number used to track work
546                                          # across threads/processes.
547  }
548  trusted_packet_sequence_id: 3903809
549}
550packet {
551  timestamp: 300
552  track_event {
553    type: TYPE_SLICE_END
554    track_uuid: 93094
555  }
556  trusted_packet_sequence_id: 3903809
557}
558packet {
559  timestamp: 400
560  track_event {
561    type: TYPE_SLICE_BEGIN
562    track_uuid: 93094
563    name: "Process background result"
564    flow_ids: 1055895987                  # Same as above.
565  }
566  trusted_packet_sequence_id: 3903809
567}
568packet {
569  timestamp: 500
570  track_event {
571    type: TYPE_SLICE_END
572    track_uuid: 93094
573  }
574  trusted_packet_sequence_id: 3903809
575}
576
577# The background thread of the process.
578packet {
579  track_descriptor {
580    uuid: 40489498
581    thread {
582      pid: 100
583      tid: 101
584      thread_name: "Background thread"
585    }
586  }
587}
588packet {
589  timestamp: 310
590  track_event {
591    type: TYPE_SLICE_BEGIN
592    track_uuid: 40489498
593    name: "Background work"
594    flow_ids: 1055895987                  # Same as above.
595  }
596  trusted_packet_sequence_id: 3903809
597}
598packet {
599  timestamp: 385
600  track_event {
601    type: TYPE_SLICE_END
602    track_uuid: 40489498
603  }
604  trusted_packet_sequence_id: 3903809
605}
606```
607
608## Counters
609
610NOTE: in the legacy JSON tracing format, this section correspond to C events.
611
612Counters are useful to represent continuous values which change with time.
613Common examples include CPU frequency, memory usage, battery charge etc.
614
615![TrackEvent counter in UI](/docs/images/synthetic-track-event-counter.png)
616
617This corresponds to the following protos:
618
619```
620# Counter track scoped to a process.
621packet {
622  track_descriptor {
623    uuid: 1388
624    process {
625      pid: 1024
626      process_name: "MySpecialProcess"
627    }
628  }
629}
630packet {
631  track_descriptor {
632    uuid: 4489498
633    parent_uuid: 1388
634    name: "My special counter"
635    counter {}
636  }
637}
638packet {
639  timestamp: 200
640  track_event {
641    type: TYPE_COUNTER
642    track_uuid: 4489498
643    counter_value: 34567    # Value at start
644  }
645  trusted_packet_sequence_id: 3903809
646}
647packet {
648  timestamp: 250
649  track_event {
650    type: TYPE_COUNTER
651    track_uuid: 4489498
652    counter_value: 67890    # Value goes up
653  }
654  trusted_packet_sequence_id: 3903809
655}
656packet {
657  timestamp: 300
658  track_event {
659    type: TYPE_COUNTER
660    track_uuid: 4489498
661    counter_value: 12345   # Value goes down
662  }
663  trusted_packet_sequence_id: 3903809
664}
665packet {
666  timestamp: 400
667  track_event {
668    type: TYPE_COUNTER
669    track_uuid: 4489498
670    counter_value: 12345   # Final value
671  }
672  trusted_packet_sequence_id: 3903809
673}
674```
675
676## Interning
677
678NOTE: there is no equivalent to interning in the JSON tracing format.
679
680Interning is an advanced but powerful feature of the protobuf tracing format
681which allows allows for reducing the number of times long strings are emitted in
682the trace.
683
684Specifically, certain fields in the protobuf format allow associating an "iid"
685(interned id) to a string and using the iid to reference the string in all
686future packets. The most commonly used cases are slice names and category names
687
688Here is an example of a trace which makes use of interning to reduce the number
689of times a very long slice name is emitted:
690![TrackEvent interning](/docs/images/synthetic-track-event-interned.png)
691
692This corresponds to the following protos:
693
694```
695packet {
696  track_descriptor {
697    uuid: 48948                         # 64-bit random number.
698    name: "My special track"
699    process {
700      pid: 1234                         # PID for your process
701      process_name: "My process name"
702    }
703  }
704}
705packet {
706  timestamp: 200
707  track_event {
708    type: TYPE_SLICE_BEGIN
709    track_uuid: 48948                   # Same random number from above.
710    name_iid: 1                         # References the string in interned_data
711                                        # (see below)
712  }
713  trusted_packet_sequence_id: 3903809   # Generate *once*, use throughout.
714
715  interned_data {
716    # Creates a mapping from the iid "1" to the string name: any |name_iid| field
717    # in this packet onwards will transparently be remapped to this string by trace
718    # processor.
719    # Note: iid 0 is *not* a valid IID and should not be used.
720    event_names {
721      iid: 1
722      name: "A very very very long slice name which we don't want to repeat"
723    }
724  }
725
726  first_packet_on_sequence: true        # Indicates to trace processor that
727                                        # this is the first packet on the
728                                        # sequence.
729  previous_packet_dropped: true         # Same as |first_packet_on_sequence|.
730
731  # Indicates to trace processor that this sequence resets the incremental state but
732  # also depends on incrtemental state state.
733  # 3 = SEQ_INCREMENTAL_STATE_CLEARED | SEQ_NEEDS_INCREMENTAL_STATE
734  sequence_flags: 3
735}
736packet {
737  timestamp: 201
738  track_event {
739    type: TYPE_SLICE_END
740    track_uuid: 48948
741  }
742  trusted_packet_sequence_id: 3903809
743}
744packet {
745  timestamp: 202
746  track_event {
747    type: TYPE_SLICE_BEGIN
748    track_uuid: 48948                   # Same random number from above.
749    name_iid: 1                         # References the string in interned_data
750                                        # above.
751  }
752  trusted_packet_sequence_id: 3903809   # Generate *once*, use throughout.
753  # 2 = SEQ_NEEDS_INCREMENTAL_STATE
754  sequence_flags: 2
755}
756packet {
757  timestamp: 203
758  track_event {
759    type: TYPE_SLICE_END
760    track_uuid: 48948
761  }
762  trusted_packet_sequence_id: 3903809
763}
764```
765