docs: add PerfettoSQL syntax page and reorg others
This CL adds a page for PerfettoSQL syntax and reorganizes other
pages and cleanup unnecessary documentation while we're here.
Change-Id: Idad2faffaae7f0dc0be70c8076a18c6dc14937b3
diff --git a/docs/analysis/builtin.md b/docs/analysis/builtin.md
index 29dd157..84deaab 100644
--- a/docs/analysis/builtin.md
+++ b/docs/analysis/builtin.md
@@ -1,4 +1,4 @@
-# Built-in Functions
+# PerfettoSQL Built-ins
These are functions built into C++ which reduce the amount of boilerplate which
needs to be written in SQL.
diff --git a/docs/analysis/common-queries.md b/docs/analysis/common-queries.md
index 018ab94..adae686 100644
--- a/docs/analysis/common-queries.md
+++ b/docs/analysis/common-queries.md
@@ -1,4 +1,4 @@
-# Common queries
+# PerfettoSQL Common Queries
This page acts as a reference guide for queries which often appear when
performing ad-hoc analysis.
diff --git a/docs/analysis/perfetto-sql-syntax.md b/docs/analysis/perfetto-sql-syntax.md
new file mode 100644
index 0000000..6b0cb2c
--- /dev/null
+++ b/docs/analysis/perfetto-sql-syntax.md
@@ -0,0 +1,103 @@
+# PerfettoSQL Syntax
+*This page documents the syntax of PerfettoSQL, a dialect of SQL used in trace
+processor and other Perfetto analysis tools to query traces.*
+
+PerfettoSQL is a direct descendent of the
+[dialect of SQL implemented by SQLite](https://www.sqlite.org/lang.html).
+Specifically, any SQL valid in SQLite is also valid in PerfettoSQL.
+
+Unfortunately, the SQLite syntax alone is not sufficient for two reasons:
+1. It is quite basic e.g. it does not support creating functions or macros
+2. It cannot be used to access features which are only available in Perfetto
+tooling e.g. it cannot be used to create efficient analytic tables, import
+modules from the PerfettoSQL standard library etc.
+
+For this reason, PerfettoSQL adds new pieces of syntax which make the experience
+of writing SQL queries better. All such additons include the keyword `PERFETTO`
+to make it clear that they are PerfettoSQL-only.
+
+<!-- TODO(b/290185551): we should really talk about our "recommendations" (e.g.
+using CREATE PERFETTO TABLE instead of CREATE TABLE) somewhere and reference it
+here. -->
+
+## Including PerfettoSQL modules
+`INCLUDE PERFETTO MODULE` is used to import all tables/views/functions/macros
+defined in a PerfettoSQL module (e.g. from the
+[PerfettoSQL standard library](/docs/analysis/stdlib-docs.autogen)).
+
+Note that this statement acts more similar to `#include` statements in C++
+rather than `import` statements from Java/Python. Specifically, all objects
+in the module become available in the global namespace without being qualified
+by the module name.
+
+Example:
+```sql
+-- Include all tables/views/functions from the android.startup.startups module
+-- in the standard library.
+INCLUDE PERFETTO MODULE android.startup.startups;
+
+-- Use the android_startups table defined in the android.startup.startups
+-- module.
+SELECT *
+FROM android_startups;
+```
+
+## Defining functions
+`CREATE PEFETTO FUNCTION` allows functions to be defined in SQL. The syntax is
+similar to the syntax in PostgreSQL or GoogleSQL.
+
+<!-- TODO(b/290185551): talk about different possible argument/return types. -->
+
+Example:
+```sql
+-- Create a scalar function with no arguments.
+CREATE PERFETTO FUNCTION constant_fn() RETURNS INT AS SELECT 1;
+
+-- Create a scalar function taking two arguments.
+CREATE PERFETTO FUNCTION add(x INT, y INT) RETURNS INT AS SELECT $x + $y;
+
+-- Create a table function with no arguments
+CREATE PERFETTO FUNCTION constant_tab_fn()
+RETURNS TABLE(ts LONG, dur LONG) AS
+SELECT column1 as ts, column2 as dur
+FROM (
+ VALUES
+ (100, 10),
+ (200, 20)
+);
+
+-- Create a table function with one argument
+CREATE PERFETTO FUNCTION sched_by_utid(utid INT)
+RETURNS TABLE(ts LONG, dur LONG, utid INT) AS
+SELECT ts, dur, utid
+FROM sched
+WHERE utid = $utid;
+```
+
+## Creating efficient tables
+`CREATE PERFETTO TABLE` allows defining tables optimized for analytic queries
+on traces. These tables are both more performant and more memory efficient than
+SQLite native tables created with `CREATE TABLE`.
+
+Note however the full feature set of `CREATE TABLE` is not supported:
+1. Perfetto tables cannot be inserted into and are read-only after creation
+2. Perfetto tables must be defined and populated using a `SELECT` statement.
+ They cannot be defined by column names and types.
+
+Example:
+```sql
+-- Create a Perfetto table with constant values.
+CREATE PERFETTO TABLE constant_table AS
+SELECT column1 as ts, column2 as dur
+FROM (
+ VALUES
+ (100, 10),
+ (200, 20)
+);
+
+-- Create a Perfetto table with a query on another table.
+CREATE PERFETTO TABLE slice_sub_table AS
+SELECT *
+FROM slice
+WHERE name = 'foo';
+```
diff --git a/docs/analysis/trace-processor.md b/docs/analysis/trace-processor.md
index f1ef185..8b5fdc2 100644
--- a/docs/analysis/trace-processor.md
+++ b/docs/analysis/trace-processor.md
@@ -509,24 +509,6 @@
The metrics subsystem is a significant part of trace processor and thus is
documented on its own [page](/docs/analysis/metrics.md).
-## Annotations
-
-TIP: To see how to add to add a new annotation to trace processor, see the
-checklist [here](/docs/contributing/common-tasks.md#new-annotation).
-
-Annotations attach a human-readable description to a slice in the trace. This
-can include information like the source of a slice, why a slice is important and
-links to documentation where the viewer can learn more about the slice.
-In essence, descriptions act as if an expert was telling the user what the slice
-means.
-
-For example, consider the `inflate` slice which occurs during view inflation in
-Android. We can add the following description and link:
-
-**Description**: Constructing a View hierarchy from pre-processed XML via
-LayoutInflater#layout. This includes constructing all of the View objects in the
-hierarchy, and applying styled attributes.
-
## Creating derived events
TIP: To see how to add to add a new annotation to trace processor, see the
@@ -557,23 +539,6 @@
The other benefit of aligning the two is that changes in metrics are
automatically kept in sync with what the user sees in the UI.
-## Alerts
-
-Alerts are used to draw the attention of the user to interesting parts of the
-trace; this are usually warnings or errors about anomalies which occurred in the
-trace.
-
-Currently, alerts are not implemented in the trace processor but the API to
-create derived events was designed with them in mind. We plan on adding another
-column `alert_type` (name to be finalized) to the annotations table which can
-have the value `warning`, `error` or `null`. Depending on this value, the
-Perfetto UI will flag these events to the user.
-
-NOTE: we do not plan on supporting case where alerts need to be added to
- existing events. Instead, new events should be created using annotations
- and alerts added on these instead; this is because the trace processor
- storage is monotonic-append-only.
-
## Python API
The trace processor Python API is built on the existing HTTP interface of `trace processor`
diff --git a/docs/toc.md b/docs/toc.md
index b0d8fc1..0ed6a88 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -1,6 +1,8 @@
-* [Overview](README.md)
+* [Introduction](README.md)
-* [Tracing 101](tracing-101.md)
+* [Overview](#)
+ * [Tracing 101](tracing-101.md)
+ * [FAQ](faq.md)
* [Quickstart](#)
* [Record traces on Android](quickstart/android-tracing.md)
@@ -11,10 +13,8 @@
* [Heap profiling](quickstart/heap-profiling.md)
* [Callstack sampling on Android](quickstart/callstack-sampling.md)
-* [FAQ](faq.md)
-
* [Case studies](#)
- * [Android boot tracing](case-studies/android-boot-tracing.md)
+ * [Tracing Android boot](case-studies/android-boot-tracing.md)
* [Debugging memory usage](case-studies/memory.md)
* [Data sources](#)
@@ -40,13 +40,14 @@
* [Interceptors](instrumentation/interceptors.md)
* [Trace analysis](#)
- * [Trace Processor (SQL)](analysis/trace-processor.md)
- * [Batch Trace Processor](analysis/batch-trace-processor.md)
- * [Standard library](analysis/stdlib-docs.autogen)
- * [Built-in Functions](analysis/builtin.md)
+ * [Trace Processor](analysis/trace-processor.md)
+ * [PerfettoSQL Syntax](analysis/perfetto-sql-syntax.md)
+ * [PerfettoSQL Standard Library](analysis/stdlib-docs.autogen)
+ * [PerfettoSQL Tables](analysis/sql-tables.autogen)
+ * [PerfettoSQL Built-ins](analysis/builtin.md)
+ * [PerfettoSQL Common Queries](analysis/common-queries.md)
* [Trace-based metrics](analysis/metrics.md)
- * [Common queries](analysis/common-queries.md)
- * [SQL tables](analysis/sql-tables.autogen)
+ * [Batch Trace Processor](analysis/batch-trace-processor.md)
* [Stats table](analysis/sql-stats.autogen)
* [Pivot tables](analysis/pivot-tables.md)
diff --git a/infra/perfetto.dev/src/gen_sql_tables_reference.js b/infra/perfetto.dev/src/gen_sql_tables_reference.js
index 2f77cea..72800b5 100644
--- a/infra/perfetto.dev/src/gen_sql_tables_reference.js
+++ b/infra/perfetto.dev/src/gen_sql_tables_reference.js
@@ -325,7 +325,8 @@
graph += '\n```\n';
}
- let md = graph;
+ let title = '# PerfettoSQL Tables\n'
+ let md = title + graph;
for (const tableGroup of tableGroups) {
md += `## ${tableGroup}\n`
for (const table of tablesByGroup[tableGroup]) {
diff --git a/infra/perfetto.dev/src/gen_stdlib_docs_md.py b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
index fe61806..7959053 100644
--- a/infra/perfetto.dev/src/gen_stdlib_docs_md.py
+++ b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
@@ -32,11 +32,11 @@
self.files_md = [
FileMd(module_name, file_dict) for file_dict in module_files
]
- self.summary_objs = "\n".join(
+ self.summary_objs = '\n'.join(
file.summary_objs for file in self.files_md if file.summary_objs)
- self.summary_funs = "\n".join(
+ self.summary_funs = '\n'.join(
file.summary_funs for file in self.files_md if file.summary_funs)
- self.summary_view_funs = "\n".join(file.summary_view_funs
+ self.summary_view_funs = '\n'.join(file.summary_view_funs
for file in self.files_md
if file.summary_view_funs)
@@ -48,13 +48,13 @@
long_s.append(f'### {file.import_key}')
if file.objs:
long_s.append('#### Views/Tables')
- long_s.append("\n".join(file.objs))
+ long_s.append('\n'.join(file.objs))
if file.funs:
long_s.append('#### Functions')
- long_s.append("\n".join(file.funs))
+ long_s.append('\n'.join(file.funs))
if file.view_funs:
- long_s.append('#### View Functions')
- long_s.append("\n".join(file.view_funs))
+ long_s.append('#### Table Functions')
+ long_s.append('\n'.join(file.view_funs))
return '\n'.join(long_s)
@@ -70,75 +70,76 @@
# Add imports if in file.
for data in file_dict['imports']:
# Anchor
- anchor = f'obj/{module_name}/{data["name"]}'
+ anchor = f'''obj/{module_name}/{data['name']}'''
# Add summary of imported view/table
- desc = data["desc"].split(".")[0]
- summary_objs_list.append(f'[{data["name"]}](#{anchor})|'
- f'{file_dict["import_key"]}|'
- f'{desc}')
+ desc = data['desc'].split('.')[0]
+ summary_objs_list.append(f'''[{data['name']}](#{anchor})|'''
+ f'''{file_dict['import_key']}|'''
+ f'''{desc}''')
- self.objs.append(f'\n\n<a name="{anchor}"></a>'
- f'**{data["name"]}**, {data["type"]}\n\n'
- f'{data["desc"]}\n')
+ self.objs.append(f'''\n\n<a name="{anchor}"></a>'''
+ f'''**{data['name']}**, {data['type']}\n\n'''
+ f'''{data['desc']}\n''')
self.objs.append('Column | Description\n------ | -----------')
for name, desc in data['cols'].items():
self.objs.append(f'{name} | {desc}')
- self.objs.append("\n\n")
+ self.objs.append('\n\n')
# Add functions if in file
for data in file_dict['functions']:
# Anchor
- anchor = f'fun/{module_name}/{data["name"]}'
+ anchor = f'''fun/{module_name}/{data['name']}'''
# Add summary of imported function
- summary_funs_list.append(f'[{data["name"]}](#{anchor})|'
- f'{file_dict["import_key"]}|'
- f'{data["return_type"]}|'
- f'{data["desc"].split(".")[0]}')
+ summary_funs_list.append(f'''[{data['name']}](#{anchor})|'''
+ f'''{file_dict['import_key']}|'''
+ f'''{data['return_type']}|'''
+ f'''{data['desc'].split('.')[0]}''')
self.funs.append(
- f'\n\n<a name="{anchor}"></a>'
- f'**{data["name"]}**\n'
- f'{data["desc"]}\n\n'
- f'Returns: {data["return_type"]}, {data["return_desc"]}\n\n')
+ f'''\n\n<a name="{anchor}"></a>'''
+ f'''**{data['name']}**\n'''
+ f'''{data['desc']}\n\n'''
+ f'''Returns: {data['return_type']}, {data['return_desc']}\n\n''')
if data['args']:
self.funs.append('Argument | Type | Description\n'
'-------- | ---- | -----------')
for name, arg_dict in data['args'].items():
- self.funs.append(f'{name} | {arg_dict["type"]} | {arg_dict["desc"]}')
+ self.funs.append(
+ f'''{name} | {arg_dict['type']} | {arg_dict['desc']}''')
- self.funs.append("\n\n")
+ self.funs.append('\n\n')
- # Add view functions if in file
+ # Add table functions if in file
for data in file_dict['view_functions']:
# Anchor
- anchor = rf'view_fun/{module_name}/{data["name"]}'
+ anchor = rf'''view_fun/{module_name}/{data['name']}'''
# Add summary of imported view function
- summary_view_funs_list.append(f'[{data["name"]}](#{anchor})|'
- f'{file_dict["import_key"]}|'
- f'{data["desc"].split(".")[0]}')
+ summary_view_funs_list.append(f'''[{data['name']}](#{anchor})|'''
+ f'''{file_dict['import_key']}|'''
+ f'''{data['desc'].split('.')[0]}''')
- self.view_funs.append(f'\n\n<a name="{anchor}"></a>'
- f'**{data["name"]}**\n'
- f'{data["desc"]}\n\n')
+ self.view_funs.append(f'''\n\n<a name="{anchor}"></a>'''
+ f'''**{data['name']}**\n'''
+ f'''{data['desc']}\n\n''')
if data['args']:
self.view_funs.append('Argument | Type | Description\n'
'-------- | ---- | -----------')
for name, arg_dict in data['args'].items():
self.view_funs.append(
- f'{name} | {arg_dict["type"]} | {arg_dict["desc"]}')
+ f'''{name} | {arg_dict['type']} | {arg_dict['desc']}''')
self.view_funs.append('\n')
self.view_funs.append('Column | Description\n' '------ | -----------')
for name, desc in data['cols'].items():
self.view_funs.append(f'{name} | {desc}')
- self.view_funs.append("\n\n")
+ self.view_funs.append('\n\n')
- self.summary_objs = "\n".join(summary_objs_list)
- self.summary_funs = "\n".join(summary_funs_list)
- self.summary_view_funs = "\n".join(summary_view_funs_list)
+ self.summary_objs = '\n'.join(summary_objs_list)
+ self.summary_funs = '\n'.join(summary_funs_list)
+ self.summary_view_funs = '\n'.join(summary_view_funs_list)
def main():
@@ -158,10 +159,44 @@
common_module = modules_dict.pop('common')
with open(args.output, 'w') as f:
- f.write("# SQL standard library\n"
- "To import any function, view_function, view or table simply run "
- "`INCLUDE PERFETTO MODULE {import key};` in your SQL query.\n"
- "## Summary\n")
+ f.write('''
+# PerfettoSQL standard library
+*This page documents the PerfettoSQL standard library.*
+
+## Introduction
+The PerfettoSQL standard library is a repository of tables, views, functions
+and macros, contributed by domain experts, which make querying traces easier
+Its design is heavily inspired by standard libraries in languages like Python,
+C++ and Java.
+
+Some of the purposes of the standard library include:
+1) Acting as a way of sharing and commonly written queries without needing
+to copy/paste large amounts of SQL.
+2) Raising the abstraction level when exposing data in the trace. Many
+modules in the standard library convert low-level trace concepts
+e.g. slices, tracks and into concepts developers may be more familar with
+e.g. for Android developers: app startups, binder transactions etc.
+
+Standard library modules can be included as follows:
+```
+-- Include all tables/views/functions from the android.startup.startups
+-- module in the standard library.
+INCLUDE PERFETTO MODULE android.startup.startups;
+
+-- Use the android_startups table defined in the android.startup.startups
+-- module.
+SELECT *
+FROM android_startups;
+```
+
+More information on importing modules is available in the
+[syntax documentation](/docs/analysis/perfetto-sql-syntax#including-perfettosql-modules)
+for the `INCLUDE PERFETTO MODULE` statement.
+
+<!-- TODO(b/290185551): talk about experimental module and contributions. -->
+
+## Summary
+''')
summary_objs = [common_module.summary_objs
] if common_module.summary_objs else []
@@ -191,21 +226,21 @@
'Name | Import | Description\n'
'---- | ------ | -----------\n')
f.write('\n'.join(summary_objs))
- f.write("\n")
+ f.write('\n')
if summary_funs:
- f.write("### Functions\n\n"
+ f.write('### Functions\n\n'
'Name | Import | Return type | Description\n'
'---- | ------ | ----------- | -----------\n')
f.write('\n'.join(summary_funs))
- f.write("\n")
+ f.write('\n')
if summary_view_funs:
- f.write("### View Functions\n\n"
+ f.write('### Table Functions\n\n'
'Name | Import | Description\n'
'---- | ------ | -----------\n')
f.write('\n'.join(summary_view_funs))
- f.write("\n")
+ f.write('\n')
f.write('\n\n')
f.write(common_module.print_description())