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 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 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 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 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 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 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