• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Trace Processor (C++)
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 GPU 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 = counter.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 track.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## Helper functions
248Helper functions are functions built into C++ which reduce the amount of
249boilerplate which needs to be written in SQL.
250
251### Extract args
252`EXTRACT_ARG` is a helper function which retreives a property of an
253event (e.g. slice or counter) from the `args` table.
254
255It takes an `arg_set_id` and `key` as input and returns the value looked
256up in the `args` table.
257
258For example, to retrieve the `prev_comm` field for `sched_switch` events in
259the `ftrace_event` table.
260```sql
261SELECT EXTRACT_ARG(arg_set_id, 'prev_comm')
262FROM ftrace_event
263WHERE name = 'sched_switch'
264```
265
266Behind the scenes, the above query would desugar to the following:
267```sql
268SELECT
269  (
270    SELECT string_value
271    FROM args
272    WHERE key = 'prev_comm' AND args.arg_set_id = raw.arg_set_id
273  )
274FROM ftrace_event
275WHERE name = 'sched_switch'
276```
277
278NOTE: while convinient, `EXTRACT_ARG` can inefficient compared to a `JOIN`
279when working with very large tables; a function call is required for every
280row which will be slower than the batch filters/sorts used by `JOIN`.
281
282## Operator tables
283SQL queries are usually sufficient to retrieve data from trace processor.
284Sometimes though, certain constructs can be difficult to express pure SQL.
285
286In these situations, trace processor has special "operator tables" which solve
287a particular problem in C++ but expose an SQL interface for queries to take
288advantage of.
289
290### Span join
291Span join is a custom operator table which computes the intersection of
292spans of time from two tables or views. A span in this concept is a row in a
293table/view which contains a "ts" (timestamp) and "dur" (duration) columns.
294
295A column (called the *partition*) can optionally be specified which divides the
296rows from each table into partitions before computing the intersection.
297
298![Span join block diagram](/docs/images/span-join.png)
299
300```sql
301-- Get all the scheduling slices
302CREATE VIEW sp_sched AS
303SELECT ts, dur, cpu, utid
304FROM sched;
305
306-- Get all the cpu frequency slices
307CREATE VIEW sp_frequency AS
308SELECT
309  ts,
310  lead(ts) OVER (PARTITION BY track_id ORDER BY ts) - ts as dur,
311  cpu,
312  value as freq
313FROM counter
314JOIN cpu_counter_track ON counter.track_id = cpu_counter_track.id
315WHERE cpu_counter_track.name = 'cpufreq';
316
317-- Create the span joined table which combines cpu frequency with
318-- scheduling slices.
319CREATE VIRTUAL TABLE sched_with_frequency
320USING SPAN_JOIN(sp_sched PARTITIONED cpu, sp_frequency PARTITIONED cpu);
321
322-- This span joined table can be queried as normal and has the columns from both
323-- tables.
324SELECT ts, dur, cpu, utid, freq
325FROM sched_with_frequency;
326```
327
328NOTE: A partition can be specified on neither, either or both tables. If
329specified on both, the same column name has to be specified on each table.
330
331WARNING: An important restriction on span joined tables is that spans from
332the same table in the same partition *cannot* overlap. For performance
333reasons, span join does not attempt to detect and error out in this situation;
334instead, incorrect rows will silently be produced.
335
336WARNING: Partitions mush be integers. Importantly, string partitions are *not*
337supported; note that strings *can* be converted to integers by
338applying the `HASH` function to the string column.
339
340Left and outer span joins are also supported; both function analogously to
341the left and outer joins from SQL.
342```sql
343-- Left table partitioned + right table unpartitioned.
344CREATE VIRTUAL TABLE left_join
345USING SPAN_LEFT_JOIN(table_a PARTITIONED a, table_b);
346
347-- Both tables unpartitioned.
348CREATE VIRTUAL TABLE outer_join
349USING SPAN_OUTER_JOIN(table_x, table_y);
350```
351
352NOTE: there is a subtlety if the partitioned table is empty and is
353either a) part of an outer join b) on the right side of a left join.
354In this case, *no* slices will be emitted even if the other table is
355non-empty. This approach was decided as being the most natural
356after considering how span joins are used in practice.
357
358### Ancestor slice
359ancestor_slice is a custom operator table that takes a
360[slice table's id column](/docs/analysis/sql-tables.autogen#slice) and computes
361all slices on the same track that are direct parents above that id (i.e. given
362a slice id it will return as rows all slices that can be found by following
363the parent_id column to the top slice (depth = 0)).
364
365The returned format is the same as the
366[slice table](/docs/analysis/sql-tables.autogen#slice)
367
368For example, the following finds the top level slice given a bunch of slices of
369interest.
370
371```sql
372CREATE VIEW interesting_slices AS
373SELECT id, ts, dur, track_id
374FROM slice WHERE name LIKE "%interesting slice name%";
375
376SELECT
377  *
378FROM
379  interesting_slices LEFT JOIN
380  ancestor_slice(interesting_slices.id) AS ancestor ON ancestor.depth = 0
381```
382
383### Ancestor slice by stack
384ancestor_slice_by_stack is a custom operator table that takes a
385[slice table's stack_id column](/docs/analysis/sql-tables.autogen#slice) and
386finds all slice ids with that stack_id, then, for each id it computes
387all the ancestor slices similarly to
388[ancestor_slice](/docs/analysis/trace-processor#ancestor-slice).
389
390The returned format is the same as the
391[slice table](/docs/analysis/sql-tables.autogen#slice)
392
393For example, the following finds the top level slice of all slices with the
394given name.
395
396```sql
397CREATE VIEW interesting_stack_ids AS
398SELECT stack_id
399FROM slice WHERE name LIKE "%interesting slice name%";
400
401SELECT
402  *
403FROM
404  interesting_stack_ids LEFT JOIN
405  ancestor_slice_by_stack(interesting_stack_ids.stack_id) AS ancestor
406  ON ancestor.depth = 0
407```
408
409### Descendant slice
410descendant_slice is a custom operator table that takes a
411[slice table's id column](/docs/analysis/sql-tables.autogen#slice) and
412computes all slices on the same track that are nested under that id (i.e.
413all slices that are on the same track at the same time frame with a depth
414greater than the given slice's depth.
415
416The returned format is the same as the
417[slice table](/docs/analysis/sql-tables.autogen#slice)
418
419For example, the following finds the number of slices under each slice of
420interest.
421
422```sql
423CREATE VIEW interesting_slices AS
424SELECT id, ts, dur, track_id
425FROM slice WHERE name LIKE "%interesting slice name%";
426
427SELECT
428  *
429  (
430    SELECT
431      COUNT(*) AS total_descendants
432    FROM descendant_slice(interesting_slice.id)
433  )
434FROM interesting_slices
435```
436
437### Descendant slice by stack
438descendant_slice_by_stack is a custom operator table that takes a
439[slice table's stack_id column](/docs/analysis/sql-tables.autogen#slice) and
440finds all slice ids with that stack_id, then, for each id it computes
441all the descendant slices similarly to
442[descendant_slice](/docs/analysis/trace-processor#descendant-slice).
443
444The returned format is the same as the
445[slice table](/docs/analysis/sql-tables.autogen#slice)
446
447For example, the following finds the next level descendant of all slices with
448the given name.
449
450```sql
451CREATE VIEW interesting_stacks AS
452SELECT stack_id, depth
453FROM slice WHERE name LIKE "%interesting slice name%";
454
455SELECT
456  *
457FROM
458  interesting_stacks LEFT JOIN
459  descendant_slice_by_stack(interesting_stacks.stack_id) AS descendant
460  ON descendant.depth = interesting_stacks.depth + 1
461```
462
463### Connected/Following/Preceding flows
464
465DIRECTLY_CONNECTED_FLOW, FOLLOWING_FLOW and PRECEDING_FLOW are custom operator
466tables that take a
467[slice table's id column](/docs/analysis/sql-tables.autogen#slice) and collect
468all entries of [flow table](/docs/analysis/sql-tables.autogen#flow), that are
469directly or indirectly connected to the given starting slice.
470
471`DIRECTLY_CONNECTED_FLOW(start_slice_id)` - contains all entries of
472[flow table](/docs/analysis/sql-tables.autogen#flow) that are present in any
473chain of kind: `flow[0] -> flow[1] -> ... -> flow[n]`, where
474`flow[i].slice_out = flow[i+1].slice_in` and `flow[0].slice_out = start_slice_id
475OR start_slice_id = flow[n].slice_in`.
476
477NOTE: Unlike the following/preceding flow functions, this function will not
478include flows connected to ancestors or descendants while searching for flows
479from a slice. It only includes the slices in the directly connected chain.
480
481`FOLLOWING_FLOW(start_slice_id)` - contains all flows which can be reached from
482a given slice via recursively following from flow's outgoing slice to its
483incoming one and from a reached slice to its child. The return table contains
484all entries of [flow table](/docs/analysis/sql-tables.autogen#flow) that are
485present in any chain of kind: `flow[0] -> flow[1] -> ... -> flow[n]`, where
486`flow[i+1].slice_out IN DESCENDANT_SLICE(flow[i].slice_in) OR
487flow[i+1].slice_out = flow[i].slice_in` and `flow[0].slice_out IN
488DESCENDANT_SLICE(start_slice_id) OR flow[0].slice_out = start_slice_id`.
489
490`PRECEDING_FLOW(start_slice_id)` - contains all flows which can be reached from
491a given slice via recursively following from flow's incoming slice to its
492outgoing one and from a reached slice to its parent. The return table contains
493all entries of [flow table](/docs/analysis/sql-tables.autogen#flow) that are
494present in any chain of kind: `flow[n] -> flow[n-1] -> ... -> flow[0]`, where
495`flow[i].slice_in IN ANCESTOR_SLICE(flow[i+1].slice_out) OR flow[i].slice_in =
496flow[i+1].slice_out` and `flow[0].slice_in IN ANCESTOR_SLICE(start_slice_id) OR
497flow[0].slice_in = start_slice_id`.
498
499```sql
500--number of following flows for each slice
501SELECT (SELECT COUNT(*) FROM FOLLOWING_FLOW(slice_id)) as following FROM slice;
502```
503
504## Metrics
505
506TIP: To see how to add to add a new metric to trace processor, see the checklist
507[here](/docs/contributing/common-tasks.md#new-metric).
508
509The metrics subsystem is a significant part of trace processor and thus is
510documented on its own [page](/docs/analysis/metrics.md).
511
512## Creating derived events
513
514TIP: To see how to add to add a new annotation to trace processor, see the
515     checklist [here](/docs/contributing/common-tasks.md#new-annotation).
516
517This feature allows creation of new events (slices and counters) from the data
518in the trace. These events can then be displayed in the UI tracks as if they
519were part of the trace itself.
520
521This is useful as often the data in the trace is very low-level. While low
522level information is important for experts to perform deep debugging, often
523users are just looking for a high level overview without needing to consider
524events from multiple locations.
525
526For example, an app startup in Android spans multiple components including
527`ActivityManager`, `system_server`, and the newly created app process derived
528from `zygote`. Most users do not need this level of detail; they are only
529interested in a single slice spanning the entire startup.
530
531Creating derived events is tied very closely to
532[metrics subsystem](/docs/analysis/metrics.md); often SQL-based metrics need to
533create higher-level abstractions from raw events as intermediate artifacts.
534
535From previous example, the
536[startup metric](/src/trace_processor/metrics/sql/android/android_startup.sql)
537creates the exact `launching` slice we want to display in the UI.
538
539The other benefit of aligning the two is that changes in metrics are
540automatically kept in sync with what the user sees in the UI.
541
542## Python API
543The trace processor's C++ library is also exposed through Python. This
544is documented on a [separate page](/docs/analysis/trace-processor-python.md).
545
546## Testing
547
548Trace processor is mainly tested in two ways:
5491. Unit tests of low-level building blocks
5502. "Diff" tests which parse traces and check the output of queries
551
552### Unit tests
553Unit testing trace processor is the same as in other parts of Perfetto and
554other C++ projects. However, unlike the rest of Perfetto, unit testing is
555relatively light in trace processor.
556
557We have discovered over time that unit tests are generally too brittle
558when dealing with code which parses traces leading to painful, mechanical
559changes being needed when refactorings happen.
560
561Because of this, we choose to focus on diff tests for most areas (e.g.
562parsing events, testing schema of tables, testing metrics etc.) and only
563use unit testing for the low-level building blocks on which the rest of
564trace processor is built.
565
566### Diff tests
567Diff tests are essentially integration tests for trace processor and the
568main way trace processor is tested.
569
570Each diff test takes as input a) a trace file b) a query file *or* a metric
571name. It runs `trace_processor_shell` to parse the trace and then executes
572the query/metric. The result is then compared to a 'golden' file and any
573difference is highlighted.
574
575All diff tests are organized under [test/trace_processor](/test/trace_processor)
576in `tests{_category name}.py` files as methods of a class in each file
577and are run by the script
578[`tools/diff_test_trace_processor.py`](/tools/diff_test_trace_processor.py).
579To add a new test its enough to add a new method starting with `test_` in suitable
580python tests file.
581
582Methods can't take arguments and have to return `DiffTestBlueprint`:
583```python
584class DiffTestBlueprint:
585  trace: Union[Path, Json, Systrace, TextProto]
586  query: Union[str, Path, Metric]
587  out: Union[Path, Json, Csv, TextProto]
588```
589*Trace* and *Out*: For every type apart from `Path`, contents of the object will be treated as
590file contents so it has to follow the same rules.
591
592*Query*: For metric tests it is enough to provide the metric name. For query tests there
593can be a raw SQL statement, for example `"SELECT * FROM SLICE"` or path to an `.sql` file.
594
595
596
597NOTE: `trace_processor_shell` and associated proto descriptors needs to be
598built before running `tools/diff_test_trace_processor.py`. The easiest way
599to do this is to run `tools/ninja -C <out directory>` both initially and on
600every change to trace processor code or builtin metrics.
601
602#### Choosing where to add diff tests
603`diff_tests/` folder contains four directories corresponding to different
604areas of trace processor.
6051. __stdlib__: Tests focusing on testing Perfetto Standard Library, both
606   prelude and the regular modules. The subdirectories in this folder
607   should generally correspond to directories in `perfetto_sql/stdlib`.
6082. __parser__: Tests focusing on ensuring that different trace files are
609   parsed correctly and the corresponding built-in tables are populated.
6103. __metrics__: Tests focusing on testing metrics located in
611   `trace_processor/metrics/sql`. This organisation is mostly historical
612   and code (and corresponding tests) is expected to move to `stdlib` over time.
6134. __syntax__: Tests focusing on testing the core syntax of PerfettoSQL
614   (i.e. `CREATE PERFETTO TABLE` or `CREATE PERFETTO FUNCTION`).
615
616__Scenario__: A new stdlib module `foo/bar.sql` is being added.
617
618_Answer_: Add the test to the `stdlib/foo/bar_tests.py` file.
619
620__Scenario__: A new event is being parsed, the focus of the test is to ensure
621the event is being parsed correctly.
622
623_Answer_: Add the test in one of the `parser` subdirectories. Prefer adding a
624test to an existing related directory (i.e. `sched`, `power`) if one exists.
625
626__Scenario__: A new metric is being added and the focus of the test is to
627ensure the metric is being correctly computed.
628
629_Answer_: Add the test in one of the `metrics` subdirectories. Prefer adding a
630test to an existing related directory if one exists. Also consider adding the
631code in question to stdlib.
632
633__Scenario__: A new dynamic table is being added and the focus of the test is to
634ensure the dynamic table is being correctly computed...
635
636_Answer_: Add the test to the `stdlib/dynamic_tables` folder
637
638__Scenario__: The interals of trace processor are being modified and the test
639is to ensure the trace processor is correctly filtering/sorting important
640built-in tables.
641
642_Answer_: Add the test to the `parser/core_tables` folder.
643
644## Appendix: table inheritance
645
646Concretely, the rules for inheritance between tables works are as follows:
647
648* Every row in a table has an `id` which is unique for a hierarchy of tables.
649  * For example, every `track` will have an `id` which is unique among all
650    tracks (regardless of the type of track)
651* If a table C inherits from P, each row in C will also be in P _with the same
652  id_
653  * This allows for ids to act as "pointers" to rows; lookups by id can be
654    performed on any table which has that row
655  * For example, every `process_counter_track` row will have a matching row in
656    `counter_track` which will itself have matching rows in `track`
657* If a table C with columns `A` and `B` inherits from P with column `A`, `A`
658  will have the same data in both C and P
659  * For example, suppose
660    *  `process_counter_track` has columns `name`, `unit` and `upid`
661    *  `counter_track` has `name` and `unit`
662    *  `track` has `name`
663  * Every row in `process_counter_track` will have the same `name`  for the row
664    with the same id in  `track` and `counter_track`
665  * Similarly, every row in `process_counter_track` will have both the same
666    `name ` and `unit` for the row with the same id in `counter_track`
667* Every row in a table has a `type` column. This specifies the _most specific_
668  table this row belongs to.
669  * This allows _dynamic casting_ of a row to its most specific type
670  * For example, for if a row in the `track` is actually a
671    `process_counter_track`, it's type column will be `process_counter_track`.
672