1# Trace Processor 2 3_The Trace Processor is a C++ library 4([/src/trace_processor](/src/trace_processor)) that ingests traces encoded in a 5wide variety of formats and exposes an SQL interface for querying trace events 6contained in a consistent set of tables. It also has other features including 7computation of summary metrics, annotating the trace with user-friendly 8descriptions and deriving new events from the contents of the trace._ 9 10![Trace processor block diagram](/docs/images/trace-processor.png) 11 12## Quickstart 13 14The [quickstart](/docs/quickstart/trace-analysis.md) provides a quick overview 15on how to run SQL queries against traces using trace processor. 16 17## Introduction 18 19Events in a trace are optimized for fast, low-overhead recording. Therefore 20traces need significant data processing to extract meaningful information from 21them. This is compounded by the number of legacy formats which are still in use and 22need to be supported in trace analysis tools. 23 24The trace processor abstracts this complexity by parsing traces, extracting the 25data inside, and exposing it in a set of database tables which can be queried 26with SQL. 27 28Features of the trace processor include: 29 30* Execution of SQL queries on a custom, in-memory, columnar database backed by 31 the SQLite query engine. 32* Metrics subsystem which allows computation of summarized view of the trace 33 (e.g. CPU or memory usage of a process, time taken for app startup etc.). 34* Annotating events in the trace with user-friendly descriptions, providing 35 context and explanation of events to newer users. 36* Creation of new events derived from the contents of the trace. 37 38The formats supported by trace processor include: 39 40* Perfetto native protobuf format 41* Linux ftrace 42* Android systrace 43* Chrome JSON (including JSON embedding Android systrace text) 44* Fuchsia binary format 45* [Ninja](https://ninja-build.org/) logs (the build system) 46 47The trace processor is embedded in a wide variety of trace analysis tools, including: 48 49* [trace_processor](/docs/analysis/trace-processor.md), a standalone binary 50 providing a shell interface (and the reference embedder). 51* [Perfetto UI](https://ui.perfetto.dev), in the form of a WebAssembly module. 52* [Android Graphics Inspector](https://gpuinspector.dev/). 53* [Android Studio](https://developer.android.com/studio/). 54 55## Concepts 56 57The trace processor has some foundational terminology and concepts which are 58used in the rest of documentation. 59 60### Events 61 62In the most general sense, a trace is simply a collection of timestamped 63"events". Events can have associated metadata and context which allows them to 64be interpreted and analyzed. 65 66Events form the foundation of trace processor and are one of two types: slices 67and counters. 68 69#### Slices 70 71![Examples of slices](/docs/images/slices.png) 72 73A slice refers to an interval of time with some data describing what was 74happening in that interval. Some example of slices include: 75 76* Scheduling slices for each CPU 77* Atrace slices on Android 78* Userspace slices from Chrome 79 80#### Counters 81 82![Examples of counters](/docs/images/counters.png) 83 84A counter is a continuous value which varies over time. Some examples of 85counters include: 86 87* CPU frequency for each CPU core 88* RSS memory events - both from the kernel and polled from /proc/stats 89* atrace counter events from Android 90* Chrome counter events 91 92### Tracks 93 94A track is a named partition of events of the same type and the same associated 95context. For example: 96 97* Scheduling slices have one track for each CPU 98* Sync userspace slice have one track for each thread which emitted an event 99* Async userspace slices have one track for each “cookie” linking a set of async 100 events 101 102The most intuitive way to think of a track is to imagine how they would be drawn 103in a UI; if all the events are in a single row, they belong to the same track. 104For example, all the scheduling events for CPU 5 are on the same track: 105 106![CPU slices track](/docs/images/cpu-slice-track.png) 107 108Tracks can be split into various types based on the type of event they contain 109and the context they are associated with. Examples include: 110 111* Global tracks are not associated to any context and contain slices 112* Thread tracks are associated to a single thread and contain slices 113* Counter tracks are not associated to any context and contain counters 114* CPU counter tracks are associated to a single CPU and contain counters 115 116### Thread and process identifiers 117 118The handling of threads and processes needs special care when considered in the 119context of tracing; identifiers for threads and processes (e.g. `pid`/`tgid` and 120`tid` in Android/macOS/Linux) can be reused by the operating system over the 121course of a trace. This means they cannot be relied upon as a unique identifier 122when querying tables in trace processor. 123 124To solve this problem, the trace processor uses `utid` (_unique_ tid) for 125threads and `upid` (_unique_ pid) for processes. All references to threads and 126processes (e.g. in CPU scheduling data, thread tracks) uses `utid` and `upid` 127instead of the system identifiers. 128 129## Object-oriented tables 130 131Modeling an object with many types is a common problem in trace processor. For 132example, tracks can come in many varieties (thread tracks, process tracks, 133counter tracks etc). Each type has a piece of data associated to it unique to 134that type; for example, thread tracks have a `utid` of the thread, counter 135tracks have the `unit` of the counter. 136 137To solve this problem in object-oriented languages, a `Track` class could be 138created and inheritance used for all subclasses (e.g. `ThreadTrack` and 139`CounterTrack` being subclasses of `Track`, `ProcessCounterTrack` being a 140subclass of `CounterTrack` etc). 141 142![Object-oriented table diagram](/docs/images/oop-table-inheritance.png) 143 144In trace processor, this "object-oriented" approach is replicated by having 145different tables for each type of object. For example, we have a `track` table 146as the "root" of the hierarchy with the `thread_track` and `counter_track` 147tables "inheriting from" the `track` table. 148 149NOTE: [The appendix below](#appendix-table-inheritance) gives the exact rules 150for inheritance between tables for interested readers. 151 152Inheritance between the tables works in the natural way (i.e. how it works in 153OO languages) and is best summarized by a diagram. 154 155![SQL table inheritance diagram](/docs/images/tp-table-inheritance.png) 156 157NOTE: For an up-to-date of how tables currently inherit from each other as well 158as a comprehensive reference of all the column and how they are inherited see 159the [SQL tables](/docs/analysis/sql-tables.autogen) reference page. 160 161## Writing Queries 162 163### Context using tracks 164 165A common question when querying tables in trace processor is: "how do I obtain 166the process or thread for a slice?". Phrased more generally, the question is 167"how do I get the context for an event?". 168 169In trace processor, any context associated with all events on a track is found 170on the associated `track` tables. 171 172For example, to obtain the `utid` of any thread which emitted a `measure` slice 173 174```sql 175SELECT utid 176FROM slice 177JOIN thread_track ON thread_track.id = slice.track_id 178WHERE slice.name = 'measure' 179``` 180 181Similarly, to obtain the `upid`s of any process which has a `mem.swap` counter 182greater than 1000 183 184```sql 185SELECT upid 186FROM counter 187JOIN process_counter_track ON process_counter_track.id = slice.track_id 188WHERE process_counter_track.name = 'mem.swap' AND value > 1000 189``` 190 191If the source and type of the event is known beforehand (which is generally the 192case), the following can be used to find the `track` table to join with 193 194| Event type | Associated with | Track table | Constraint in WHERE clause | 195| :--------- | ------------------ | --------------------- | -------------------------- | 196| slice | N/A (global scope) | track | `type = 'track'` | 197| slice | thread | thread_track | N/A | 198| slice | process | process_track | N/A | 199| counter | N/A (global scope) | counter_track | `type = 'counter_track'` | 200| counter | thread | thread_counter_track | N/A | 201| counter | process | process_counter_track | N/A | 202| counter | cpu | cpu_counter_track | N/A | 203 204On the other hand, sometimes the source is not known. In this case, joining with 205the `track `table and looking up the `type` column will give the exact track 206table to join with. 207 208For example, to find the type of track for `measure` events, the following query 209could be used. 210 211```sql 212SELECT type 213FROM slice 214JOIN track ON track.id = slice.track_id 215WHERE slice.name = 'measure' 216``` 217 218### Thread and process tables 219 220While obtaining `utid`s and `upid`s are a step in the right direction, generally 221users want the original `tid`, `pid`, and process/thread names. 222 223The `thread` and `process` tables map `utid`s and `upid`s to threads and 224processes respectively. For example, to lookup the thread with `utid` 10 225 226```sql 227SELECT tid, name 228FROM thread 229WHERE utid = 10 230``` 231 232The `thread` and `process` tables can also be joined with the associated track 233tables directly to jump directly from the slice or counter to the information 234about processes and threads. 235 236For example, to get a list of all the threads which emitted a `measure` slice 237 238```sql 239SELECT thread.name AS thread_name 240FROM slice 241JOIN thread_track ON slice.track_id = thread_track.id 242JOIN thread USING(utid) 243WHERE slice.name = 'measure' 244GROUP BY thread_name 245``` 246 247## Operator tables 248SQL queries are usually sufficient to retrieve data from trace processor. 249Sometimes though, certain constructs can be difficult to express pure SQL. 250 251In these situations, trace processor has special "operator tables" which solve 252a particular problem in C++ but expose an SQL interface for queries to take 253advantage of. 254 255### Span join 256Span join is a custom operator table which computes the intersection of 257spans of time from two tables or views. A column (called the *partition*) 258can optionally be specified which divides the rows from each table into 259partitions before computing the intersection. 260 261![Span join block diagram](/docs/images/span-join.png) 262 263```sql 264-- Get all the scheduling slices 265CREATE VIEW sp_sched AS 266SELECT ts, dur, cpu, utid 267FROM sched 268 269-- Get all the cpu frequency slices 270CREATE VIEW sp_frequency AS 271SELECT 272 ts, 273 lead(ts) OVER (PARTITION BY cpu ORDER BY ts) - ts as dur, 274 cpu, 275 value as freq 276FROM counter 277 278-- Create the span joined table which combines cpu frequency with 279-- scheduling slices. 280CREATE VIRTUAL TABLE sched_with_frequency 281USING SPAN_JOIN(sp_sched PARTITIONED cpu, sp_frequency PARTITIONED cpu) 282 283-- This span joined table can be queried as normal and has the columns from both 284-- tables. 285SELECT ts, dur, cpu, utid, freq 286FROM sched_with_frequency 287``` 288 289NOTE: A partition can be specified on neither, either or both tables. If 290specified on both, the same column name has to be specified on each table. 291 292WARNING: An important restriction on span joined tables is that spans from 293the same table in the same partition *cannot* overlap. For performance 294reasons, span join does attempt to dectect and error out in this situation; 295instead, incorrect rows will silently be produced. 296 297### Ancestor slice 298ancestor_slice is a custom operator table that takes a 299[slice table's id column](/docs/analysis/sql-tables.autogen#slice) and computes 300all slices on the same track that are direct parents above that id (i.e. given 301a slice id it will return as rows all slices that can be found by following 302the parent_id column to the top slice (depth = 0)). 303 304The returned format is the same as the 305[slice table](/docs/analysis/sql-tables.autogen#slice) 306 307For example, the following finds the top level slice given a bunch of slices of 308interest. 309 310```sql 311CREATE VIEW interesting_slices AS 312SELECT id, ts, dur, track_id 313FROM slice WHERE name LIKE "%interesting slice name%"; 314 315SELECT 316 * 317FROM 318 interesting_slices LEFT JOIN 319 ancestor_slice(interesting_slices.id) AS ancestor ON ancestor.depth = 0 320``` 321 322### Descendant slice 323descendant_slice is a custom operator table that takes a 324[slice table's id column](/docs/analysis/sql-tables.autogen#slice) and 325computes all slices on the same track that are nested under that id (i.e. 326all slices that are on the same track at the same time frame with a depth 327greater than the given slice's depth. 328 329The returned format is the same as the 330[slice table](/docs/analysis/sql-tables.autogen#slice) 331 332For example, the following finds the number of slices under each slice of 333interest. 334 335```sql 336CREATE VIEW interesting_slices AS 337SELECT id, ts, dur, track_id 338FROM slice WHERE name LIKE "%interesting slice name%"; 339 340SELECT 341 * 342 ( 343 SELECT 344 COUNT(*) AS total_descendants 345 FROM descendant_slice(interesting_slice.id) 346 ) 347FROM interesting_slices 348``` 349 350### Following/Preceding/Connected flows 351following_flow, preceding_flow, connected_flow are custom operator tables that 352take a [slice table's id column](/docs/analysis/sql-tables.autogen#slice) and 353collect all entries of [flow table](/docs/analysis/sql-tables.autogen#flow), 354that are directly or indirectly connected to the given starting slice. 355 356`FOLLOWING_FLOW(start_slice_id)` - contains all entries of 357[flow table](/docs/analysis/sql-tables.autogen#flow) 358that are present in any chain of kind: `flow[0] -> flow[1] -> ... -> flow[n]`, 359where `flow[i].slice_out = flow[i+1].slice_in` and 360`flow[0].slice_out = start_slice_id`. 361 362`PRECEDING_FLOW(start_slice_id)` - contains all entries of 363[flow table](/docs/analysis/sql-tables.autogen#flow) 364that are present in any chain of kind: `flow[n] -> flow[n-1] -> ... -> flow[0]`, 365where `flow[i].slice_in = flow[i+1].slice_out` and 366`flow[0].slice_in = start_slice_id`. 367 368`CONNECTED_FLOW(start_slice_id)` - contains a union of both 369`FOLLOWING_FLOW(start_slice_id)` and `PRECEDING_FLOW(start_slice_id)` tables. 370 371```sql 372--number of following flows for each slice 373SELECT (SELECT COUNT(*) FROM FOLLOWING_FLOW(slice_id)) as following FROM slice; 374``` 375 376## Metrics 377 378TIP: To see how to add to add a new metric to trace processor, see the checklist 379[here](/docs/contributing/common-tasks.md#new-metric). 380 381The metrics subsystem is a significant part of trace processor and thus is 382documented on its own [page](/docs/analysis/metrics.md). 383 384## Annotations 385 386TIP: To see how to add to add a new annotation to trace processor, see the 387checklist [here](/docs/contributing/common-tasks.md#new-annotation). 388 389Annotations attach a human-readable description to a slice in the trace. This 390can include information like the source of a slice, why a slice is important and 391links to documentation where the viewer can learn more about the slice. 392In essence, descriptions act as if an expert was telling the user what the slice 393means. 394 395For example, consider the `inflate` slice which occurs during view inflation in 396Android. We can add the following description and link: 397 398**Description**: Constructing a View hierarchy from pre-processed XML via 399LayoutInflater#layout. This includes constructing all of the View objects in the 400hierarchy, and applying styled attributes. 401 402## Creating derived events 403 404TIP: To see how to add to add a new annotation to trace processor, see the 405 checklist [here](/docs/contributing/common-tasks.md#new-annotation). 406 407This feature allows creation of new events (slices and counters) from the data 408in the trace. These events can then be displayed in the UI tracks as if they 409were part of the trace itself. 410 411This is useful as often the data in the trace is very low-level. While low 412level information is important for experts to perform deep debugging, often 413users are just looking for a high level overview without needing to consider 414events from multiple locations. 415 416For example, an app startup in Android spans multiple components including 417`ActivityManager`, `system_server`, and the newly created app process derived 418from `zygote`. Most users do not need this level of detail; they are only 419interested in a single slice spanning the entire startup. 420 421Creating derived events is tied very closely to 422[metrics subsystem](/docs/analysis/metrics.md); often SQL-based metrics need to 423create higher-level abstractions from raw events as intermediate artifacts. 424 425From previous example, the 426[startup metric](/src/trace_processor/metrics/android/android_startup.sql) 427creates the exact `launching` slice we want to display in the UI. 428 429The other benefit of aligning the two is that changes in metrics are 430automatically kept in sync with what the user sees in the UI. 431 432## Alerts 433 434Alerts are used to draw the attention of the user to interesting parts of the 435trace; this are usually warnings or errors about anomalies which occurred in the 436trace. 437 438Currently, alerts are not implemented in the trace processor but the API to 439create derived events was designed with them in mind. We plan on adding another 440column `alert_type` (name to be finalized) to the annotations table which can 441have the value `warning`, `error` or `null`. Depending on this value, the 442Perfetto UI will flag these events to the user. 443 444NOTE: we do not plan on supporting case where alerts need to be added to 445 existing events. Instead, new events should be created using annotations 446 and alerts added on these instead; this is because the trace processor 447 storage is monotonic-append-only. 448 449## Python API 450 451The trace processor Python API is built on the existing HTTP interface of `trace processor` 452and is available as part of the standalone build. The API allows you to load in traces and 453query tables and run metrics without requiring the `trace_processor` binary to be 454downloaded or installed. 455 456### Setup 457``` 458pip install perfetto 459``` 460NOTE: The API is only compatible with Python3. 461 462```python 463from perfetto.trace_processor import TraceProcessor 464# Initialise TraceProcessor with a trace file 465tp = TraceProcessor(file_path='trace.pftrace') 466``` 467 468NOTE: The TraceProcessor can be initialized in a combination of ways including: 469 <br> - An address at which there exists a running instance of `trace_processor` with a 470 loaded trace (e.g. `TraceProcessor(addr='localhost:9001')`) 471 <br> - An address at which there exists a running instance of `trace_processor` and 472 needs a trace to be loaded in 473 (e.g. `TraceProcessor(addr='localhost:9001', file_path='trace.pftrace')`) 474 <br> - A path to a `trace_processor` binary and the trace to be loaded in 475 (e.g. `TraceProcessor(bin_path='./trace_processor', file_path='trace.pftrace')`) 476 477 478### API 479 480The `trace_processor.api` module contains the `TraceProcessor` class which provides various 481functions that can be called on the loaded trace. For more information on how to use 482these functions, see this [`example`](/src/trace_processor/python/example.py). 483 484#### Query 485The query() function takes an SQL query as input and returns an iterator through the rows 486of the result. 487 488```python 489from perfetto.trace_processor import TraceProcessor 490tp = TraceProcessor(file_path='trace.pftrace') 491 492qr_it = tp.query('SELECT ts, dur, name FROM slice') 493for row in qr_it: 494 print(row.ts, row.dur, row.name) 495``` 496**Output** 497``` 498261187017446933 358594 eglSwapBuffersWithDamageKHR 499261187017518340 357 onMessageReceived 500261187020825163 9948 queueBuffer 501261187021345235 642 bufferLoad 502261187121345235 153 query 503... 504``` 505The QueryResultIterator can also be converted to a Pandas DataFrame, although this 506requires you to have both the `NumPy` and `Pandas` modules installed. 507```python 508from perfetto.trace_processor import TraceProcessor 509tp = TraceProcessor(file_path='trace.pftrace') 510 511qr_it = tp.query('SELECT ts, dur, name FROM slice') 512qr_df = qr_it.as_pandas_dataframe() 513print(qr_df.to_string()) 514``` 515**Output** 516``` 517ts dur name 518-------------------- -------------------- --------------------------- 519 261187017446933 358594 eglSwapBuffersWithDamageKHR 520 261187017518340 357 onMessageReceived 521 261187020825163 9948 queueBuffer 522 261187021345235 642 bufferLoad 523 261187121345235 153 query 524 ... 525``` 526Furthermore, you can use the query result in a Pandas DataFrame format to easily 527make visualisations from the trace data. 528```python 529from perfetto.trace_processor import TraceProcessor 530tp = TraceProcessor(file_path='trace.pftrace') 531 532qr_it = tp.query('SELECT ts, value FROM counter WHERE track_id=50') 533qr_df = qr_it.as_pandas_dataframe() 534qr_df = qr_df.replace(np.nan,0) 535qr_df = qr_df.set_index('ts')['value'].plot() 536``` 537**Output** 538 539![Graph made frpm the query results](/docs/images/example_pd_graph.png) 540 541 542#### Metric 543The metric() function takes in a list of trace metrics and returns the results as a Protobuf. 544 545```python 546from perfetto.trace_processor import TraceProcessor 547tp = TraceProcessor(file_path='trace.pftrace') 548 549ad_cpu_metrics = tp.metric(['android_cpu']) 550print(ad_cpu_metrics) 551``` 552**Output** 553``` 554metrics { 555 android_cpu { 556 process_info { 557 name: "/system/bin/init" 558 threads { 559 name: "init" 560 core { 561 id: 1 562 metrics { 563 mcycles: 1 564 runtime_ns: 570365 565 min_freq_khz: 1900800 566 max_freq_khz: 1900800 567 avg_freq_khz: 1902017 568 } 569 } 570 core { 571 id: 3 572 metrics { 573 mcycles: 0 574 runtime_ns: 366406 575 min_freq_khz: 1900800 576 max_freq_khz: 1900800 577 avg_freq_khz: 1902908 578 } 579 } 580 ... 581 } 582 ... 583 } 584 process_info { 585 name: "/system/bin/logd" 586 threads { 587 name: "logd.writer" 588 core { 589 id: 0 590 metrics { 591 mcycles: 8 592 runtime_ns: 33842357 593 min_freq_khz: 595200 594 max_freq_khz: 1900800 595 avg_freq_khz: 1891825 596 } 597 } 598 core { 599 id: 1 600 metrics { 601 mcycles: 9 602 runtime_ns: 36019300 603 min_freq_khz: 1171200 604 max_freq_khz: 1900800 605 avg_freq_khz: 1887969 606 } 607 } 608 ... 609 } 610 ... 611 } 612 ... 613 } 614} 615``` 616 617### HTTP 618The `trace_processor.http` module contains the `TraceProcessorHttp` class which 619provides methods to make HTTP requests to an address at which there already 620exists a running instance of `trace_processor` with a trace loaded in. All 621results are returned in Protobuf format 622(see [`trace_processor_proto`](/protos/perfetto/trace_processor/trace_processor.proto)). 623Some functions include: 624* `execute_query()` - Takes in an SQL query and returns a `QueryResult` Protobuf 625 message 626* `compute_metric()` - Takes in a list of trace metrics and returns a 627 `ComputeMetricResult` Protobuf message 628* `status()` - Returns a `StatusResult` Protobuf message 629 630 631## Testing 632 633Trace processor is mainly tested in two ways: 6341. Unit tests of low-level building blocks 6352. "Diff" tests which parse traces and check the output of queries 636 637### Unit tests 638Unit testing trace processor is the same as in other parts of Perfetto and 639other C++ projects. However, unlike the rest of Perfetto, unit testing is 640relatively light in trace processor. 641 642We have discovered over time that unit tests are generally too brittle 643when dealing with code which parses traces leading to painful, mechanical 644changes being needed when refactorings happen. 645 646Because of this, we choose to focus on diff tests for most areas (e.g. 647parsing events, testing schema of tables, testing metrics etc.) and only 648use unit testing for the low-level building blocks on which the rest of 649trace processor is built. 650 651### Diff tests 652Diff tests are essentially integration tests for trace processor and the 653main way trace processor is tested. 654 655Each diff test takes as input a) a trace file b) a query file *or* a metric 656name. It runs `trace_processor_shell` to parse the trace and then executes 657the query/metric. The result is then compared to a 'golden' file and any 658difference is highlighted. 659 660All diff tests are organized under [test/trace_processor](/test/trace_processor) 661and are run by the script 662[`tools/diff_test_trace_processor.py`](/tools/diff_test_trace_processor.py). 663New tests can be added with the helper script 664[`tools/add_tp_diff_test.py`](/tools/add_tp_diff_test.py). 665 666NOTE: `trace_processor_shell` and associated proto descriptors needs to be 667built before running `tools/diff_test_trace_processor.py`. The easiest way 668to do this is to run `tools/ninja -C <out directory>` both initially and on 669every change to trace processor code or builtin metrics. 670 671#### Choosing where to add diff tests 672When adding a new test with `tools/add_tp_diff_test.py`, the user is 673prompted for a folder to add the new test to. Often this can be confusing 674as a test can fall into more than one category. This section is a guide 675to decide which folder to choose. 676 677Broadly, there are two categories which all folders fall into: 6781. __"Area" folders__ which encompass a "vertical" area of interest 679 e.g. startup/ contains Android app startup related tests or chrome/ 680 contains all Chrome related tests. 6812. __"Feature" folders__ which encompass a particular feature of 682 trace processor e.g. process_tracking/ tests the lifetime tracking of 683 processes, span_join/ tests the span join operator. 684 685"Area" folders should be preferred for adding tests unless the test is 686applicable to more than one "area"; in this case, one of "feature" folders 687can be used instead. 688 689Here are some common scenarios in which new tests may be added and 690answers on where to add the test: 691 692__Scenario__: A new event is being parsed, the focus of the test is to ensure 693the event is being parsed correctly and the event is focused on a single 694vertical "Area". 695 696_Answer_: Add the test in one of the "Area" folders. 697 698__Scenario__: A new event is being parsed and the focus of the test is to ensure 699the event is being parsed correctly and the event is applicable to more than one 700vertical "Area". 701 702_Answer_: Add the test to the parsing/ folder. 703 704__Scenario__: A new metric is being added and the focus of the test is to 705ensure the metric is being correctly computed. 706 707_Answer_: Add the test in one of the "Area" folders. 708 709__Scenario__: A new dynamic table is being added and the focus of the test is to 710ensure the dynamic table is being correctly computed... 711 712_Answer_: Add the test to the dynamic/ folder 713 714__Scenario__: The interals of trace processor are being modified and the test 715is to ensure the trace processor is correctly filtering/sorting important 716built-in tables. 717 718_Answer_: Add the test to the tables/ folder. 719 720 721## Appendix: table inheritance 722 723Concretely, the rules for inheritance between tables works are as follows: 724 725* Every row in a table has an `id` which is unique for a hierarchy of tables. 726 * For example, every `track` will have an `id` which is unique among all 727 tracks (regardless of the type of track) 728* If a table C inherits from P, each row in C will also be in P _with the same 729 id_ 730 * This allows for ids to act as "pointers" to rows; lookups by id can be 731 performed on any table which has that row 732 * For example, every `process_counter_track` row will have a matching row in 733 `counter_track` which will itself have matching rows in `track` 734* If a table C with columns `A` and `B` inherits from P with column `A`, `A` 735 will have the same data in both C and P 736 * For example, suppose 737 * `process_counter_track` has columns `name`, `unit` and `upid` 738 * `counter_track` has `name` and `unit` 739 * `track` has `name` 740 * Every row in `process_counter_track` will have the same `name` for the row 741 with the same id in `track` and `counter_track` 742 * Similarly, every row in `process_counter_track` will have both the same 743 `name ` and `unit` for the row with the same id in `counter_track` 744* Every row in a table has a `type` column. This specifies the _most specific_ 745 table this row belongs to. 746 * This allows _dynamic casting_ of a row to its most specific type 747 * For example, for if a row in the `track` is actually a 748 `process_counter_track`, it's type column will be `process_counter_track`. 749