Merge "Replace nbsp with space in queries." into main
diff --git a/Android.bp b/Android.bp
index b78249c..8e65554 100644
--- a/Android.bp
+++ b/Android.bp
@@ -10358,10 +10358,10 @@
     name: "perfetto_src_trace_processor_perfetto_sql_engine_engine",
     srcs: [
         "src/trace_processor/perfetto_sql/engine/created_function.cc",
-        "src/trace_processor/perfetto_sql/engine/created_table_function.cc",
         "src/trace_processor/perfetto_sql/engine/function_util.cc",
         "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc",
         "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc",
+        "src/trace_processor/perfetto_sql/engine/runtime_table_function.cc",
     ],
 }
 
@@ -10427,7 +10427,7 @@
 filegroup {
     name: "perfetto_src_trace_processor_perfetto_sql_intrinsics_table_functions_interface",
     srcs: [
-        "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.cc",
+        "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.cc",
     ],
 }
 
diff --git a/BUILD b/BUILD
index a28f7f3..70af987 100644
--- a/BUILD
+++ b/BUILD
@@ -2104,14 +2104,14 @@
     srcs = [
         "src/trace_processor/perfetto_sql/engine/created_function.cc",
         "src/trace_processor/perfetto_sql/engine/created_function.h",
-        "src/trace_processor/perfetto_sql/engine/created_table_function.cc",
-        "src/trace_processor/perfetto_sql/engine/created_table_function.h",
         "src/trace_processor/perfetto_sql/engine/function_util.cc",
         "src/trace_processor/perfetto_sql/engine/function_util.h",
         "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc",
         "src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h",
         "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc",
         "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h",
+        "src/trace_processor/perfetto_sql/engine/runtime_table_function.cc",
+        "src/trace_processor/perfetto_sql/engine/runtime_table_function.h",
     ],
 )
 
@@ -2167,8 +2167,8 @@
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_intrinsics_table_functions_interface",
     srcs = [
-        "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.cc",
-        "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h",
+        "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.cc",
+        "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h",
     ],
 )
 
diff --git a/docs/contributing/ui-plugins.md b/docs/contributing/ui-plugins.md
new file mode 100644
index 0000000..7a674e8
--- /dev/null
+++ b/docs/contributing/ui-plugins.md
@@ -0,0 +1,81 @@
+# UI plugins
+The Perfetto UI can be extended with plugins. These plugins are shipped
+part of Perfetto.
+
+## Create a plugin
+The guide below explains how to create a plugin for the Perfetto UI.
+
+### Prepare for UI development
+First we need to prepare the UI development environment.
+You will need to use a MacOS or Linux machine.
+Follow the steps below or see the
+[Getting Started](./getting-started) guide for more detail.
+
+```sh
+git clone https://android.googlesource.com/platform/external/perfetto/
+cd perfetto
+./tool/install-build-deps --ui
+```
+
+### Copy the plugin skeleton
+```sh
+cp -r ui/plugins/com.example.Skeleton ui/plugins/<your-plugin-name>
+```
+Now edit `ui/plugins/<your-plugin-name>/index.ts`.
+Search for all instances of `SKELETON: <instruction>` in the file and
+follow the instructions.
+
+Notes on naming:
+- Don't name the directory `XyzPlugin` just `Xyz`.
+- The `pluginId` and directory name must match.
+- Plugins should be prefixed with the reversed components of a domain
+  name you control. For example if `example.com` is your domain your
+  plugin should be named `com.example.Foo`.
+- Core plugins maintained by the Perfetto team should use
+  `dev.perfetto.Foo`.
+
+### Start the dev server
+```sh
+./ui/run-dev-server
+```
+Now navigate to [](http://localhost:10000/settings)
+
+### Upload your plugin for review
+- Update `ui/plugins/<your-plugin-name>/OWNERS` to include your email.
+- Follow the [Contributing](./getting-started#contributing)
+  instructions to upload your CL to the codereview tool.
+- Once uploaded add `hjd@google.com` as a reviewer for your CL.
+
+## Plugin extension points
+Plugins can extend a handful of specific places in the UI. The sections
+below show these extension points and give examples of how they can be
+used.
+
+### Commands
+TBD
+
+### Tracks
+TBD
+
+### Detail tabs
+TBD
+
+### Metric Visualisations
+TBD
+
+## Guide to the plugin API
+TBD
+
+## Default plugins
+TBD
+
+## Misc notes
+- Plugins must be licensed under
+  [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
+  the same as all other code in the repository.
+- Plugins are the responsibility of the OWNERS of that plugin to
+  maintain, not the responsibility of the Perfetto team. All
+  efforts will be made to keep the plugin API stable and existing
+  plugins working however plugins that remain unmaintained for long
+  periods of time will be disabled and ultimately deleted.
+
diff --git a/docs/faq.md b/docs/faq.md
index df41ff3..89491ec 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -20,7 +20,7 @@
 tools/open_trace_in_ui -i /path/to/trace
 ```
 
-## Why does Perfetto not support <some obscure JSON format feature>?
+## Why does Perfetto not support \<some obscure JSON format feature\>?
 
 The JSON trace format is considered a legacy trace format and is supported on a
 best-effort basis. While we try our best to maintain compatibility with the
diff --git a/docs/toc.md b/docs/toc.md
index 6fe3ae9..b0d8fc1 100644
--- a/docs/toc.md
+++ b/docs/toc.md
@@ -75,11 +75,12 @@
     * [Getting started](contributing/getting-started.md)
     * [Build instructions](contributing/build-instructions.md)
     * [Running tests](contributing/testing.md)
-    * [Common tasks](contributing/common-tasks.md)
+    * [UI plugins](contributing/ui-plugins.md)
+    * [UI development hints](contributing/ui-development.md)
     * [Embedding Perfetto](contributing/embedding.md)
     * [Releasing the SDK](contributing/sdk-releasing.md)
     * [Chrome branches](contributing/chrome-branches.md)
-    * [UI development](contributing/ui-development.md)
+    * [Common tasks](contributing/common-tasks.md)
     * [Press](contributing/perfetto-in-the-press.md)
 
 * [Design documents](#)
diff --git a/examples/sdk/README.md b/examples/sdk/README.md
index 3519006..21b64e2 100644
--- a/examples/sdk/README.md
+++ b/examples/sdk/README.md
@@ -67,6 +67,10 @@
 ```bash
 export NDK=/path/to/ndk
 cmake -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
+      -DANDROID_ABI=arm64-v8a \
+      -DANDROID_PLATFORM=android-21 \
+      -DANDROID_LD=lld \
+      -DCMAKE_BUILD_TYPE=Release \
       -B build_android
 cmake --build build_android
 ```
diff --git a/examples/sdk/example_system_wide.cc b/examples/sdk/example_system_wide.cc
index 87fb7a5..177b38b 100644
--- a/examples/sdk/example_system_wide.cc
+++ b/examples/sdk/example_system_wide.cc
@@ -91,6 +91,7 @@
   // are going to use the system-wide tracing service, so that we can see our
   // app's events in context with system profiling information.
   args.backends = perfetto::kSystemBackend;
+  args.enable_system_consumer = false;
 
   perfetto::Tracing::Initialize(args);
   perfetto::TrackEvent::Register();
diff --git a/examples/shared_lib/example_shlib_data_source.c b/examples/shared_lib/example_shlib_data_source.c
index f9e4040..d8aa1f7 100644
--- a/examples/shared_lib/example_shlib_data_source.c
+++ b/examples/shared_lib/example_shlib_data_source.c
@@ -29,7 +29,7 @@
   PerfettoProducerInit(args);
 
   PerfettoDsRegister(&custom, "com.example.custom_data_source",
-                     PerfettoDsNoCallbacks());
+                     PerfettoDsParamsDefault());
 
   for (;;) {
     PERFETTO_DS_TRACE(custom, ctx) {
diff --git a/include/perfetto/public/abi/data_source_abi.h b/include/perfetto/public/abi/data_source_abi.h
index 9eae031..a3a60aa 100644
--- a/include/perfetto/public/abi/data_source_abi.h
+++ b/include/perfetto/public/abi/data_source_abi.h
@@ -172,6 +172,27 @@
 PERFETTO_SDK_EXPORT void PerfettoDsSetCbUserArg(struct PerfettoDsImpl*,
                                                 void* user_arg);
 
+enum PerfettoDsBufferExhaustedPolicy {
+  // If the data source runs out of space when trying to acquire a new chunk,
+  // it will drop data.
+  PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP = 0,
+  // If the data source runs out of space when trying to acquire a new chunk,
+  // it will stall, retry and eventually abort if a free chunk is not acquired
+  // after a while.
+  PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1,
+};
+
+// If the data source doesn't find an empty chunk when trying to emit tracing
+// data, it will behave according to `policy` (which is a `enum
+// PerfettoDsBufferExhaustedPolicy`).
+//
+// Should not be called after PerfettoDsImplRegister().
+//
+// Returns true if successful, false otherwise.
+PERFETTO_SDK_EXPORT bool PerfettoDsSetBufferExhaustedPolicy(
+    struct PerfettoDsImpl*,
+    uint32_t policy);
+
 // Registers the `*ds_impl` data source type.
 //
 // `ds_impl` must be obtained via a call to `PerfettoDsImplCreate()`.
diff --git a/include/perfetto/public/abi/producer_abi.h b/include/perfetto/public/abi/producer_abi.h
index 94dc134..a939b73 100644
--- a/include/perfetto/public/abi/producer_abi.h
+++ b/include/perfetto/public/abi/producer_abi.h
@@ -31,6 +31,19 @@
 // Initializes the global in-process perfetto producer.
 PERFETTO_SDK_EXPORT void PerfettoProducerInProcessInit(void);
 
+// Informs the tracing services to activate any of these triggers if any tracing
+// session was waiting for them.
+//
+// `trigger_names` is an array of `const char*` (zero terminated strings). The
+// last pointer in the array must be NULL.
+//
+// Sends the trigger signal to all the initialized backends that are currently
+// connected and that connect in the next `ttl_ms` milliseconds (but
+// returns immediately anyway).
+PERFETTO_SDK_EXPORT void PerfettoProducerActivateTriggers(
+    const char* trigger_names[],
+    uint32_t ttl_ms);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/perfetto/public/abi/tracing_session_abi.h b/include/perfetto/public/abi/tracing_session_abi.h
index 113ac9b..4446bb8 100644
--- a/include/perfetto/public/abi/tracing_session_abi.h
+++ b/include/perfetto/public/abi/tracing_session_abi.h
@@ -41,6 +41,15 @@
     void* cfg_begin,
     size_t cfg_len);
 
+typedef void (*PerfettoTracingSessionStopCb)(struct PerfettoTracingSessionImpl*,
+                                             void* user_arg);
+
+// Calls `*cb` with `user_arg` when the tracing session is stopped.
+PERFETTO_SDK_EXPORT void PerfettoTracingSessionSetStopCb(
+    struct PerfettoTracingSessionImpl*,
+    PerfettoTracingSessionStopCb cb,
+    void* user_arg);
+
 PERFETTO_SDK_EXPORT void PerfettoTracingSessionStartAsync(
     struct PerfettoTracingSessionImpl*);
 
diff --git a/include/perfetto/public/data_source.h b/include/perfetto/public/data_source.h
index b244df1..65f1256 100644
--- a/include/perfetto/public/data_source.h
+++ b/include/perfetto/public/data_source.h
@@ -22,9 +22,11 @@
 
 #include "perfetto/public/abi/atomic.h"
 #include "perfetto/public/abi/data_source_abi.h"
+#include "perfetto/public/abi/heap_buffer.h"
 #include "perfetto/public/compiler.h"
 #include "perfetto/public/pb_msg.h"
 #include "perfetto/public/pb_utils.h"
+#include "perfetto/public/protos/common/data_source_descriptor.pzc.h"
 #include "perfetto/public/protos/trace/trace_packet.pzc.h"
 
 // A data source type.
@@ -40,7 +42,7 @@
   { &perfetto_atomic_false, PERFETTO_NULL }
 
 // All the callbacks are optional and can be NULL if not needed.
-struct PerfettoDsCallbacks {
+struct PerfettoDsParams {
   // Instance lifecycle callbacks:
   PerfettoDsOnSetupCb on_setup_cb;
   PerfettoDsOnStartCb on_start_cb;
@@ -60,75 +62,87 @@
 
   // Passed to all the callbacks as the `user_arg` param.
   void* user_arg;
+
+  // How to behave when running out of shared memory buffer space.
+  enum PerfettoDsBufferExhaustedPolicy buffer_exhausted_policy;
 };
 
-static inline struct PerfettoDsCallbacks PerfettoDsNoCallbacks(void) {
-  struct PerfettoDsCallbacks ret = {
-      PERFETTO_NULL, PERFETTO_NULL, PERFETTO_NULL, PERFETTO_NULL, PERFETTO_NULL,
-      PERFETTO_NULL, PERFETTO_NULL, PERFETTO_NULL, PERFETTO_NULL};
+static inline struct PerfettoDsParams PerfettoDsParamsDefault(void) {
+  struct PerfettoDsParams ret = {
+      PERFETTO_NULL, PERFETTO_NULL,
+      PERFETTO_NULL, PERFETTO_NULL,
+      PERFETTO_NULL, PERFETTO_NULL,
+      PERFETTO_NULL, PERFETTO_NULL,
+      PERFETTO_NULL, PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP};
   return ret;
 }
 
 // Registers the data source type `ds`, named `data_source_name` with the global
 // perfetto producer.
-//
-// `callbacks` are called when certain events happen on the data source type.
-// PerfettoDsNoCallbacks() can be used if callbacks are not needed.
-//
-// TODO(ddiproietto): Accept the full DataSourceDescriptor, not just the
-// data_source_name
 static inline bool PerfettoDsRegister(struct PerfettoDs* ds,
                                       const char* data_source_name,
-                                      struct PerfettoDsCallbacks callbacks) {
+                                      struct PerfettoDsParams params) {
   struct PerfettoDsImpl* ds_impl;
   bool success;
-  // Build the DataSourceDescriptor protobuf message.
-  size_t data_source_name_len = strlen(data_source_name);
-  uint8_t* data_source_desc = PERFETTO_STATIC_CAST(
-      uint8_t*, malloc(data_source_name_len + PERFETTO_PB_VARINT_MAX_SIZE_32 +
-                       PERFETTO_PB_VARINT_MAX_SIZE_64));
-  uint8_t* write_ptr = data_source_desc;
-  const int32_t name_field_id = 1;  // perfetto.protos.DataSourceDescriptor.name
-  write_ptr = PerfettoPbWriteVarInt(
-      PerfettoPbMakeTag(name_field_id, PERFETTO_PB_WIRE_TYPE_DELIMITED),
-      write_ptr);
-  write_ptr = PerfettoPbWriteVarInt(data_source_name_len, write_ptr);
-  memcpy(write_ptr, data_source_name, data_source_name_len);
-  write_ptr += data_source_name_len;
+  void* desc_buf;
+  size_t desc_size;
+
+  ds->enabled = &perfetto_atomic_false;
+  ds->impl = PERFETTO_NULL;
+
+  {
+    struct PerfettoPbMsgWriter writer;
+    struct PerfettoHeapBuffer* hb = PerfettoHeapBufferCreate(&writer.writer);
+    struct perfetto_protos_DataSourceDescriptor desc;
+    PerfettoPbMsgInit(&desc.msg, &writer);
+
+    perfetto_protos_DataSourceDescriptor_set_cstr_name(&desc, data_source_name);
+
+    desc_size = PerfettoStreamWriterGetWrittenSize(&writer.writer);
+    desc_buf = malloc(desc_size);
+    PerfettoHeapBufferCopyInto(hb, &writer.writer, desc_buf, desc_size);
+    PerfettoHeapBufferDestroy(hb, &writer.writer);
+  }
+
+  if (!desc_buf) {
+    return false;
+  }
 
   ds_impl = PerfettoDsImplCreate();
-  if (callbacks.on_setup_cb) {
-    PerfettoDsSetOnSetupCallback(ds_impl, callbacks.on_setup_cb);
+  if (params.on_setup_cb) {
+    PerfettoDsSetOnSetupCallback(ds_impl, params.on_setup_cb);
   }
-  if (callbacks.on_start_cb) {
-    PerfettoDsSetOnStartCallback(ds_impl, callbacks.on_start_cb);
+  if (params.on_start_cb) {
+    PerfettoDsSetOnStartCallback(ds_impl, params.on_start_cb);
   }
-  if (callbacks.on_stop_cb) {
-    PerfettoDsSetOnStopCallback(ds_impl, callbacks.on_stop_cb);
+  if (params.on_stop_cb) {
+    PerfettoDsSetOnStopCallback(ds_impl, params.on_stop_cb);
   }
-  if (callbacks.on_flush_cb) {
-    PerfettoDsSetOnFlushCallback(ds_impl, callbacks.on_flush_cb);
+  if (params.on_flush_cb) {
+    PerfettoDsSetOnFlushCallback(ds_impl, params.on_flush_cb);
   }
-  if (callbacks.on_create_tls_cb) {
-    PerfettoDsSetOnCreateTls(ds_impl, callbacks.on_create_tls_cb);
+  if (params.on_create_tls_cb) {
+    PerfettoDsSetOnCreateTls(ds_impl, params.on_create_tls_cb);
   }
-  if (callbacks.on_delete_tls_cb) {
-    PerfettoDsSetOnDeleteTls(ds_impl, callbacks.on_delete_tls_cb);
+  if (params.on_delete_tls_cb) {
+    PerfettoDsSetOnDeleteTls(ds_impl, params.on_delete_tls_cb);
   }
-  if (callbacks.on_create_incr_cb) {
-    PerfettoDsSetOnCreateIncr(ds_impl, callbacks.on_create_incr_cb);
+  if (params.on_create_incr_cb) {
+    PerfettoDsSetOnCreateIncr(ds_impl, params.on_create_incr_cb);
   }
-  if (callbacks.on_delete_incr_cb) {
-    PerfettoDsSetOnDeleteIncr(ds_impl, callbacks.on_delete_incr_cb);
+  if (params.on_delete_incr_cb) {
+    PerfettoDsSetOnDeleteIncr(ds_impl, params.on_delete_incr_cb);
   }
-  if (callbacks.user_arg) {
-    PerfettoDsSetCbUserArg(ds_impl, callbacks.user_arg);
+  if (params.user_arg) {
+    PerfettoDsSetCbUserArg(ds_impl, params.user_arg);
+  }
+  if (params.buffer_exhausted_policy !=
+      PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) {
+    PerfettoDsSetBufferExhaustedPolicy(ds_impl, params.buffer_exhausted_policy);
   }
 
-  success = PerfettoDsImplRegister(
-      ds_impl, &ds->enabled, data_source_desc,
-      PERFETTO_STATIC_CAST(size_t, write_ptr - data_source_desc));
-  free(data_source_desc);
+  success = PerfettoDsImplRegister(ds_impl, &ds->enabled, desc_buf, desc_size);
+  free(desc_buf);
   if (!success) {
     return false;
   }
diff --git a/include/perfetto/public/producer.h b/include/perfetto/public/producer.h
index 4545baa..56692c6 100644
--- a/include/perfetto/public/producer.h
+++ b/include/perfetto/public/producer.h
@@ -17,8 +17,11 @@
 #ifndef INCLUDE_PERFETTO_PUBLIC_PRODUCER_H_
 #define INCLUDE_PERFETTO_PUBLIC_PRODUCER_H_
 
+#include <stdint.h>
+
 #include "perfetto/public/abi/backend_type.h"
 #include "perfetto/public/abi/producer_abi.h"
+#include "perfetto/public/compiler.h"
 
 // Arguments for PerfettoProducerInit. This struct is not ABI-stable, fields can
 // be added and rearranged.
@@ -37,4 +40,18 @@
   }
 }
 
+// Informs the tracing services to activate the single trigger `trigger_name` if
+// any tracing session was waiting for it.
+//
+// Sends the trigger signal to all the initialized backends that are currently
+// connected and that connect in the next `ttl_ms` milliseconds (but
+// returns immediately anyway).
+static inline void PerfettoProducerActivateTrigger(const char* trigger_name,
+                                                   uint32_t ttl_ms) {
+  const char* trigger_names[2];
+  trigger_names[0] = trigger_name;
+  trigger_names[1] = PERFETTO_NULL;
+  PerfettoProducerActivateTriggers(trigger_names, ttl_ms);
+}
+
 #endif  // INCLUDE_PERFETTO_PUBLIC_PRODUCER_H_
diff --git a/include/perfetto/public/protos/BUILD.gn b/include/perfetto/public/protos/BUILD.gn
index 5901a27..64d324f 100644
--- a/include/perfetto/public/protos/BUILD.gn
+++ b/include/perfetto/public/protos/BUILD.gn
@@ -25,5 +25,6 @@
     "trace/track_event/debug_annotation.pzc.h",
     "trace/track_event/track_descriptor.pzc.h",
     "trace/track_event/track_event.pzc.h",
+    "trace/trigger.pzc.h",
   ]
 }
diff --git a/include/perfetto/public/protos/common/data_source_descriptor.pzc.h b/include/perfetto/public/protos/common/data_source_descriptor.pzc.h
new file mode 100644
index 0000000..21e62fb
--- /dev/null
+++ b/include/perfetto/public/protos/common/data_source_descriptor.pzc.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Autogenerated by the ProtoZero C compiler plugin.
+// Invoked by tools/gen_c_protos
+// DO NOT EDIT.
+#ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_COMMON_DATA_SOURCE_DESCRIPTOR_PZC_H_
+#define INCLUDE_PERFETTO_PUBLIC_PROTOS_COMMON_DATA_SOURCE_DESCRIPTOR_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_MSG_DECL(perfetto_protos_FtraceDescriptor);
+PERFETTO_PB_MSG_DECL(perfetto_protos_GpuCounterDescriptor);
+PERFETTO_PB_MSG_DECL(perfetto_protos_TrackEventDescriptor);
+
+PERFETTO_PB_MSG(perfetto_protos_DataSourceDescriptor);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
+                  STRING,
+                  const char*,
+                  name,
+                  1);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
+                  VARINT,
+                  uint64_t,
+                  id,
+                  7);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
+                  VARINT,
+                  bool,
+                  will_notify_on_stop,
+                  2);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
+                  VARINT,
+                  bool,
+                  will_notify_on_start,
+                  3);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
+                  VARINT,
+                  bool,
+                  handles_incremental_state_clear,
+                  4);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
+                  MSG,
+                  perfetto_protos_GpuCounterDescriptor,
+                  gpu_counter_descriptor,
+                  5);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
+                  MSG,
+                  perfetto_protos_TrackEventDescriptor,
+                  track_event_descriptor,
+                  6);
+PERFETTO_PB_FIELD(perfetto_protos_DataSourceDescriptor,
+                  MSG,
+                  perfetto_protos_FtraceDescriptor,
+                  ftrace_descriptor,
+                  8);
+
+#endif  // INCLUDE_PERFETTO_PUBLIC_PROTOS_COMMON_DATA_SOURCE_DESCRIPTOR_PZC_H_
diff --git a/include/perfetto/public/protos/trace/trigger.pzc.h b/include/perfetto/public/protos/trace/trigger.pzc.h
new file mode 100644
index 0000000..1ad6749
--- /dev/null
+++ b/include/perfetto/public/protos/trace/trigger.pzc.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRIGGER_PZC_H_
+#define INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRIGGER_PZC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "perfetto/public/pb_macros.h"
+
+PERFETTO_PB_MSG(perfetto_protos_Trigger);
+PERFETTO_PB_FIELD(perfetto_protos_Trigger,
+                  STRING,
+                  const char*,
+                  trigger_name,
+                  1);
+PERFETTO_PB_FIELD(perfetto_protos_Trigger,
+                  STRING,
+                  const char*,
+                  producer_name,
+                  2);
+PERFETTO_PB_FIELD(perfetto_protos_Trigger,
+                  VARINT,
+                  int32_t,
+                  trusted_producer_uid,
+                  3);
+
+#endif  // INCLUDE_PERFETTO_PUBLIC_PROTOS_TRACE_TRIGGER_PZC_H_
diff --git a/infra/ci/controller/controller.py b/infra/ci/controller/controller.py
index 7a3f79a..fed3b01 100644
--- a/infra/ci/controller/controller.py
+++ b/infra/ci/controller/controller.py
@@ -339,10 +339,10 @@
         for (job_id, status) in failed_jobs.iteritems()
     ])
   if passed_jobs:
-    msg += 'PASS:\n'
+    msg += '#\nPASS:\n'
     msg += ''.join(['- %s/%s\n' % (log_url, job_id) for job_id in passed_jobs])
   if ui_links:
-    msg += 'Artifacts:\n' + ''.join('- %s\n' % link for link in ui_links)
+    msg += '\nArtifacts:\n' + ''.join('- %s\n' % link for link in ui_links)
   msg += 'CI page for this CL:\n'
   msg += '- https://ci.perfetto.dev/#!/cls/%s\n' % cl_and_ps.split('-')[0]
   body = {'labels': {}, 'message': msg}
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 474cb98..f0801da 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -750,6 +750,7 @@
     TASK_TYPE_LOW_PRIORITY_SCRIPT_EXECUTION = 81;
     TASK_TYPE_STORAGE = 82;
     TASK_TYPE_NETWORKING_UNFREEZABLE_IMAGE_LOADING = 83;
+    TASK_TYPE_MAIN_THREAD_TASK_QUEUE_V8_LOW_PRIORITY = 84;
   }
 
   enum FrameType {
@@ -806,6 +807,9 @@
   // This is same as LatencyInfo's trace_id, using the name event_latency_id to
   // move away from the generic trace_id name used at other places as well.
   optional int64 event_latency_id = 4;
+  // This is set only for scroll updates and is based on the
+  // Event.ScrollJank.DelayedFramesPercentage.FixedWindow metric.
+  optional bool is_janky_scrolled_frame = 5;
 }
 
 message ProcessSingleton {
@@ -950,6 +954,8 @@
 
     UI_BEFORE_UNLOAD_BROWSER_RESPONSE_TQ = 54;
     IO_BEFORE_UNLOAD_BROWSER_RESPONSE_TQ = 55;
+
+    V8_LOW_PRIORITY_TQ = 56;
   }
 
   optional Priority priority = 1;
diff --git a/python/generators/diff_tests/testing.py b/python/generators/diff_tests/testing.py
index 2e42b52..89aea8a 100644
--- a/python/generators/diff_tests/testing.py
+++ b/python/generators/diff_tests/testing.py
@@ -219,3 +219,20 @@
         for method in methods
         if method.__name__.startswith('test_')
     ]
+
+def PrintProfileProto(profile):
+  locations = {l.id: l for l in profile.location}
+  functions = {f.id: f for f in profile.function}
+  samples = []
+  for s in profile.sample:
+    stack = []
+    for location in [locations[id] for id in s.location_id]:
+      for function in [functions[l.function_id] for l in location.line]:
+        stack.append("{name} ({address})".format(
+            name=profile.string_table[function.name],
+            address=hex(location.address)))
+      if len(location.line) == 0:
+        stack.append("({address})".format(address=hex(location.address)))
+    samples.append('Sample:\nValues: {values}\nStack:\n{stack}'.format(
+        values=', '.join(map(str, s.value)), stack='\n'.join(stack)))
+  return '\n\n'.join(sorted(samples)) + '\n'
diff --git a/python/tools/check_imports.py b/python/tools/check_imports.py
index b4a1a32..8313702 100755
--- a/python/tools/check_imports.py
+++ b/python/tools/check_imports.py
@@ -144,16 +144,21 @@
         r'/controller/.*',
         'trying to reduce the dependency mess as we refactor into core',
     ),
-
-    # Fails at the moment due to:
-    # ui/src/base/comparison_utils.ts
-    #    -> ui/src/common/query_result.ts
-    #    -> ui/src/core/static_initializers.ts
-    #NoDep(
-    #  r'/base/.*',
-    #  r'/core/.*',
-    #  'core should depend on base not the other way round',
-    #),
+    NoDep(
+        r'/base/.*',
+        r'/core/.*',
+        'core should depend on base not the other way round',
+    ),
+    NoDep(
+        r'/base/.*',
+        r'/common/.*',
+        'common should depend on base not the other way round',
+    ),
+    NoDep(
+        r'/common/.*',
+        r'/chrome_extension/.*',
+        'chrome_extension must be a leaf',
+    ),
 
     # Fails at the moment as we have several circular dependencies. One
     # example:
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index e8db554..89ca409 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -233,14 +233,13 @@
     deps += [ "//third_party/fuchsia-sdk/sdk/pkg/fdio" ]
   }
   if (perfetto_build_standalone || perfetto_build_with_android) {
+    sources += [ "unix_socket_unittest.cc" ]
+    deps += [ ":unix_socket" ]
+
     # This causes some problems on the chromium waterfall.
     if (is_linux || is_android) {
       sources += [ "watchdog_unittest.cc" ]
     }
-    if (!is_win) {
-      sources += [ "unix_socket_unittest.cc" ]
-      deps += [ ":unix_socket" ]
-    }
   }
 }
 
diff --git a/src/shared_lib/data_source.cc b/src/shared_lib/data_source.cc
index 1482f56..1e46c32 100644
--- a/src/shared_lib/data_source.cc
+++ b/src/shared_lib/data_source.cc
@@ -18,6 +18,7 @@
 
 #include <bitset>
 
+#include "perfetto/tracing/buffer_exhausted_policy.h"
 #include "perfetto/tracing/data_source.h"
 #include "perfetto/tracing/internal/basic_types.h"
 #include "protos/perfetto/common/data_source_descriptor.gen.h"
@@ -60,6 +61,9 @@
   // Passed to all the callbacks as the `user_arg` param.
   void* cb_user_arg;
 
+  perfetto::BufferExhaustedPolicy buffer_exhausted_policy =
+      perfetto::BufferExhaustedPolicy::kDrop;
+
   DataSourceType cpp_type;
   std::atomic<bool> enabled{false};
   std::mutex mu;
@@ -262,6 +266,24 @@
   ds_impl->cb_user_arg = user_arg;
 }
 
+bool PerfettoDsSetBufferExhaustedPolicy(struct PerfettoDsImpl* ds_impl,
+                                        uint32_t policy) {
+  if (ds_impl->IsRegistered()) {
+    return false;
+  }
+
+  switch (policy) {
+    case PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP:
+      ds_impl->buffer_exhausted_policy = perfetto::BufferExhaustedPolicy::kDrop;
+      return true;
+    case PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT:
+      ds_impl->buffer_exhausted_policy =
+          perfetto::BufferExhaustedPolicy::kStall;
+      return true;
+  }
+  return false;
+}
+
 bool PerfettoDsImplRegister(struct PerfettoDsImpl* ds_impl,
                             PERFETTO_ATOMIC(bool) * *enabled_ptr,
                             const void* descriptor,
@@ -295,7 +317,7 @@
   params.supports_multiple_instances = true;
   params.requires_callbacks_under_lock = false;
   bool success = data_source_type->cpp_type.Register(
-      dsd, factory, params, perfetto::BufferExhaustedPolicy::kDrop,
+      dsd, factory, params, data_source_type->buffer_exhausted_policy,
       create_custom_tls_fn, create_incremental_state_fn, cb_ctx);
   if (!success) {
     return false;
diff --git a/src/shared_lib/producer.cc b/src/shared_lib/producer.cc
index e4991b8..ebd28b2 100644
--- a/src/shared_lib/producer.cc
+++ b/src/shared_lib/producer.cc
@@ -46,3 +46,12 @@
   args.backends = perfetto::kSystemBackend;
   perfetto::Tracing::Initialize(args);
 }
+
+void PerfettoProducerActivateTriggers(const char* trigger_names[],
+                                      uint32_t ttl_ms) {
+  std::vector<std::string> triggers;
+  for (size_t i = 0; trigger_names[i] != nullptr; i++) {
+    triggers.push_back(trigger_names[i]);
+  }
+  perfetto::Tracing::ActivateTriggers(triggers, ttl_ms);
+}
diff --git a/src/shared_lib/test/api_integrationtest.cc b/src/shared_lib/test/api_integrationtest.cc
index 96ce34c..3ef111e 100644
--- a/src/shared_lib/test/api_integrationtest.cc
+++ b/src/shared_lib/test/api_integrationtest.cc
@@ -14,18 +14,21 @@
  * limitations under the License.
  */
 
-#include <condition_variable>
-#include <mutex>
 #include <thread>
 
 #include "perfetto/public/abi/data_source_abi.h"
+#include "perfetto/public/abi/heap_buffer.h"
 #include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/abi/tracing_session_abi.h"
 #include "perfetto/public/data_source.h"
 #include "perfetto/public/pb_decoder.h"
 #include "perfetto/public/producer.h"
+#include "perfetto/public/protos/config/data_source_config.pzc.h"
+#include "perfetto/public/protos/config/trace_config.pzc.h"
 #include "perfetto/public/protos/trace/test_event.pzc.h"
 #include "perfetto/public/protos/trace/trace.pzc.h"
 #include "perfetto/public/protos/trace/trace_packet.pzc.h"
+#include "perfetto/public/protos/trace/trigger.pzc.h"
 
 #include "test/gtest_and_gmock.h"
 
@@ -188,7 +191,7 @@
     args.backends = PERFETTO_BACKEND_IN_PROCESS;
     PerfettoProducerInit(args);
     PerfettoDsRegister(&data_source_1, kDataSourceName1,
-                       PerfettoDsNoCallbacks());
+                       PerfettoDsParamsDefault());
     RegisterDataSource2();
   }
 
@@ -208,36 +211,36 @@
   };
 
   void RegisterDataSource2() {
-    static struct PerfettoDsCallbacks callbacks = {};
-    callbacks.on_setup_cb = [](struct PerfettoDsImpl* ds_impl,
-                               PerfettoDsInstanceIndex inst_id, void* ds_config,
-                               size_t ds_config_size, void* user_arg) -> void* {
+    struct PerfettoDsParams params = PerfettoDsParamsDefault();
+    params.on_setup_cb = [](struct PerfettoDsImpl* ds_impl,
+                            PerfettoDsInstanceIndex inst_id, void* ds_config,
+                            size_t ds_config_size, void* user_arg) -> void* {
       auto* thiz = static_cast<SharedLibDataSourceTest*>(user_arg);
       return thiz->ds2_callbacks_.OnSetup(ds_impl, inst_id, ds_config,
                                           ds_config_size, thiz->ds2_user_arg_);
     };
-    callbacks.on_start_cb = [](struct PerfettoDsImpl* ds_impl,
-                               PerfettoDsInstanceIndex inst_id, void* user_arg,
-                               void* inst_ctx) -> void {
+    params.on_start_cb = [](struct PerfettoDsImpl* ds_impl,
+                            PerfettoDsInstanceIndex inst_id, void* user_arg,
+                            void* inst_ctx) -> void {
       auto* thiz = static_cast<SharedLibDataSourceTest*>(user_arg);
       return thiz->ds2_callbacks_.OnStart(ds_impl, inst_id, thiz->ds2_user_arg_,
                                           inst_ctx);
     };
-    callbacks.on_stop_cb =
+    params.on_stop_cb =
         [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
            void* user_arg, void* inst_ctx, struct PerfettoDsOnStopArgs* args) {
           auto* thiz = static_cast<SharedLibDataSourceTest*>(user_arg);
           return thiz->ds2_callbacks_.OnStop(
               ds_impl, inst_id, thiz->ds2_user_arg_, inst_ctx, args);
         };
-    callbacks.on_flush_cb =
+    params.on_flush_cb =
         [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
            void* user_arg, void* inst_ctx, struct PerfettoDsOnFlushArgs* args) {
           auto* thiz = static_cast<SharedLibDataSourceTest*>(user_arg);
           return thiz->ds2_callbacks_.OnFlush(
               ds_impl, inst_id, thiz->ds2_user_arg_, inst_ctx, args);
         };
-    callbacks.on_create_tls_cb =
+    params.on_create_tls_cb =
         [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
            struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
       auto* thiz = static_cast<SharedLibDataSourceTest*>(user_arg);
@@ -247,12 +250,12 @@
                                                        thiz->ds2_user_arg_);
       return state;
     };
-    callbacks.on_delete_tls_cb = [](void* ptr) {
+    params.on_delete_tls_cb = [](void* ptr) {
       auto* state = static_cast<Ds2CustomState*>(ptr);
       state->thiz->ds2_callbacks_.OnDeleteTls(state->actual);
       delete state;
     };
-    callbacks.on_create_incr_cb =
+    params.on_create_incr_cb =
         [](struct PerfettoDsImpl* ds_impl, PerfettoDsInstanceIndex inst_id,
            struct PerfettoDsTracerImpl* tracer, void* user_arg) -> void* {
       auto* thiz = static_cast<SharedLibDataSourceTest*>(user_arg);
@@ -262,13 +265,13 @@
           ds_impl, inst_id, tracer, thiz->ds2_user_arg_);
       return state;
     };
-    callbacks.on_delete_incr_cb = [](void* ptr) {
+    params.on_delete_incr_cb = [](void* ptr) {
       auto* state = static_cast<Ds2CustomState*>(ptr);
       state->thiz->ds2_callbacks_.OnDeleteIncr(state->actual);
       delete state;
     };
-    callbacks.user_arg = this;
-    PerfettoDsRegister(&data_source_2, kDataSourceName2, callbacks);
+    params.user_arg = this;
+    PerfettoDsRegister(&data_source_2, kDataSourceName2, params);
   }
 
   void* Ds2ActualCustomState(void* ptr) {
@@ -558,4 +561,77 @@
   PERFETTO_DS_TRACE(data_source_1, ctx) {}
 }
 
+class SharedLibProducerTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    struct PerfettoProducerInitArgs args = {0};
+    args.backends = PERFETTO_BACKEND_IN_PROCESS;
+    PerfettoProducerInit(args);
+  }
+
+  void TearDown() override { perfetto::shlib::ResetForTesting(); }
+};
+
+TEST_F(SharedLibDataSourceTest, ActivateTriggers) {
+  struct PerfettoPbMsgWriter writer;
+  struct PerfettoHeapBuffer* hb = PerfettoHeapBufferCreate(&writer.writer);
+
+  struct perfetto_protos_TraceConfig cfg;
+  PerfettoPbMsgInit(&cfg.msg, &writer);
+  {
+    struct perfetto_protos_TraceConfig_BufferConfig buffers;
+    perfetto_protos_TraceConfig_begin_buffers(&cfg, &buffers);
+    perfetto_protos_TraceConfig_BufferConfig_set_size_kb(&buffers, 1024);
+    perfetto_protos_TraceConfig_end_buffers(&cfg, &buffers);
+  }
+  {
+    struct perfetto_protos_TraceConfig_TriggerConfig trigger_config;
+    perfetto_protos_TraceConfig_begin_trigger_config(&cfg, &trigger_config);
+    perfetto_protos_TraceConfig_TriggerConfig_set_trigger_mode(
+        &trigger_config,
+        perfetto_protos_TraceConfig_TriggerConfig_STOP_TRACING);
+    perfetto_protos_TraceConfig_TriggerConfig_set_trigger_timeout_ms(
+        &trigger_config, 5000);
+    {
+      struct perfetto_protos_TraceConfig_TriggerConfig_Trigger trigger;
+      perfetto_protos_TraceConfig_TriggerConfig_begin_triggers(&trigger_config,
+                                                               &trigger);
+      perfetto_protos_TraceConfig_TriggerConfig_Trigger_set_cstr_name(
+          &trigger, "trigger1");
+      perfetto_protos_TraceConfig_TriggerConfig_end_triggers(&trigger_config,
+                                                             &trigger);
+    }
+    perfetto_protos_TraceConfig_end_trigger_config(&cfg, &trigger_config);
+  }
+  size_t cfg_size = PerfettoStreamWriterGetWrittenSize(&writer.writer);
+  std::unique_ptr<uint8_t[]> ser(new uint8_t[cfg_size]);
+  PerfettoHeapBufferCopyInto(hb, &writer.writer, ser.get(), cfg_size);
+  PerfettoHeapBufferDestroy(hb, &writer.writer);
+
+  struct PerfettoTracingSessionImpl* ts =
+      PerfettoTracingSessionCreate(PERFETTO_BACKEND_IN_PROCESS);
+
+  PerfettoTracingSessionSetup(ts, ser.get(), cfg_size);
+
+  PerfettoTracingSessionStartBlocking(ts);
+  TracingSession tracing_session = TracingSession::Adopt(ts);
+
+  const char* triggers[3];
+  triggers[0] = "trigger0";
+  triggers[1] = "trigger1";
+  triggers[2] = nullptr;
+  PerfettoProducerActivateTriggers(triggers, 10000);
+
+  tracing_session.WaitForStopped();
+  std::vector<uint8_t> data = tracing_session.ReadBlocking();
+  EXPECT_THAT(FieldView(data),
+              Contains(PbField(
+                  perfetto_protos_Trace_packet_field_number,
+                  MsgField(Contains(PbField(
+                      perfetto_protos_TracePacket_trigger_field_number,
+                      MsgField(Contains(PbField(
+                          perfetto_protos_Trigger_trigger_name_field_number,
+                          StringField("trigger1"))))))))));
+}
+
 }  // namespace
diff --git a/src/shared_lib/test/benchmark.cc b/src/shared_lib/test/benchmark.cc
index b30e86f..4861a31 100644
--- a/src/shared_lib/test/benchmark.cc
+++ b/src/shared_lib/test/benchmark.cc
@@ -45,7 +45,7 @@
   struct PerfettoProducerInitArgs args = {0};
   args.backends = PERFETTO_BACKEND_IN_PROCESS;
   PerfettoProducerInit(args);
-  PerfettoDsRegister(&custom, kDataSourceName, PerfettoDsNoCallbacks());
+  PerfettoDsRegister(&custom, kDataSourceName, PerfettoDsParamsDefault());
   return true;
 }
 
diff --git a/src/shared_lib/test/utils.cc b/src/shared_lib/test/utils.cc
index a479078..69013bd 100644
--- a/src/shared_lib/test/utils.cc
+++ b/src/shared_lib/test/utils.cc
@@ -85,22 +85,37 @@
 
   PerfettoTracingSessionStartBlocking(ts);
 
+  return TracingSession::Adopt(ts);
+}
+
+TracingSession TracingSession::Adopt(
+    struct PerfettoTracingSessionImpl* session) {
   TracingSession ret;
-  ret.session_ = ts;
+  ret.session_ = session;
+  ret.stopped_ = std::make_unique<WaitableEvent>();
+  PerfettoTracingSessionSetStopCb(
+      ret.session_,
+      [](struct PerfettoTracingSessionImpl*, void* arg) {
+        static_cast<WaitableEvent*>(arg)->Notify();
+      },
+      ret.stopped_.get());
   return ret;
 }
 
 TracingSession::TracingSession(TracingSession&& other) noexcept {
   session_ = other.session_;
   other.session_ = nullptr;
+  stopped_ = std::move(other.stopped_);
+  other.stopped_ = nullptr;
 }
 
 TracingSession::~TracingSession() {
   if (!session_) {
     return;
   }
-  if (!stopped_) {
+  if (!stopped_->IsNotified()) {
     PerfettoTracingSessionStopBlocking(session_);
+    stopped_->WaitForNotification();
   }
   PerfettoTracingSessionDestroy(session_);
 }
@@ -124,8 +139,11 @@
   return result;
 }
 
+void TracingSession::WaitForStopped() {
+  stopped_->WaitForNotification();
+}
+
 void TracingSession::StopBlocking() {
-  stopped_ = true;
   PerfettoTracingSessionStopBlocking(session_);
 }
 
diff --git a/src/shared_lib/test/utils.h b/src/shared_lib/test/utils.h
index 5197b19..875162f 100644
--- a/src/shared_lib/test/utils.h
+++ b/src/shared_lib/test/utils.h
@@ -22,6 +22,7 @@
 #include <cstdint>
 #include <functional>
 #include <iterator>
+#include <memory>
 #include <mutex>
 #include <ostream>
 #include <string>
@@ -78,6 +79,9 @@
    private:
     std::string data_source_name_;
   };
+
+  static TracingSession Adopt(struct PerfettoTracingSessionImpl*);
+
   TracingSession(TracingSession&&) noexcept;
 
   ~TracingSession();
@@ -85,13 +89,14 @@
   struct PerfettoTracingSessionImpl* session() const { return session_; }
 
   bool FlushBlocking(uint32_t timeout_ms);
+  void WaitForStopped();
   void StopBlocking();
   std::vector<uint8_t> ReadBlocking();
 
  private:
   TracingSession() = default;
   struct PerfettoTracingSessionImpl* session_;
-  bool stopped_ = false;
+  std::unique_ptr<WaitableEvent> stopped_;
 };
 
 template <typename FieldSkipper>
diff --git a/src/shared_lib/tracing_session.cc b/src/shared_lib/tracing_session.cc
index 8a8ee82..28872e6 100644
--- a/src/shared_lib/tracing_session.cc
+++ b/src/shared_lib/tracing_session.cc
@@ -45,6 +45,13 @@
   ts->Setup(cfg);
 }
 
+void PerfettoTracingSessionSetStopCb(struct PerfettoTracingSessionImpl* session,
+                                     PerfettoTracingSessionStopCb cb,
+                                     void* user_arg) {
+  auto* ts = reinterpret_cast<perfetto::TracingSession*>(session);
+  ts->SetOnStopCallback([session, cb, user_arg]() { cb(session, user_arg); });
+}
+
 void PerfettoTracingSessionStartAsync(
     struct PerfettoTracingSessionImpl* session) {
   auto* ts = reinterpret_cast<perfetto::TracingSession*>(session);
diff --git a/src/trace_processor/perfetto_sql/engine/BUILD.gn b/src/trace_processor/perfetto_sql/engine/BUILD.gn
index 1e8950c..29d355f 100644
--- a/src/trace_processor/perfetto_sql/engine/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/engine/BUILD.gn
@@ -20,14 +20,14 @@
   sources = [
     "created_function.cc",
     "created_function.h",
-    "created_table_function.cc",
-    "created_table_function.h",
     "function_util.cc",
     "function_util.h",
     "perfetto_sql_engine.cc",
     "perfetto_sql_engine.h",
     "perfetto_sql_parser.cc",
     "perfetto_sql_parser.h",
+    "runtime_table_function.cc",
+    "runtime_table_function.h",
   ]
   deps = [
     "../..:metatrace",
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index c2de3e3..dcb99fe 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -26,9 +26,9 @@
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/perfetto_sql/engine/created_function.h"
-#include "src/trace_processor/perfetto_sql/engine/created_table_function.h"
 #include "src/trace_processor/perfetto_sql/engine/function_util.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h"
+#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
 #include "src/trace_processor/sqlite/db_sqlite_table.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_source.h"
@@ -93,8 +93,8 @@
 
 PerfettoSqlEngine::PerfettoSqlEngine(StringPool* pool)
     : query_cache_(new QueryCache()), pool_(pool), engine_(new SqliteEngine()) {
-  engine_->RegisterVirtualTableModule<CreatedTableFunction>(
-      "created_table_function", this, SqliteTable::TableType::kExplicitCreate,
+  engine_->RegisterVirtualTableModule<RuntimeTableFunction>(
+      "runtime_table_function", this, SqliteTable::TableType::kExplicitCreate,
       false);
 }
 
@@ -102,11 +102,11 @@
   // Destroying the sqlite engine should also destroy all the created table
   // functions.
   engine_.reset();
-  PERFETTO_CHECK(created_table_function_state_.size() == 0);
+  PERFETTO_CHECK(runtime_table_fn_states_.size() == 0);
 }
 
-void PerfettoSqlEngine::RegisterTable(const Table& table,
-                                      const std::string& table_name) {
+void PerfettoSqlEngine::RegisterStaticTable(const Table& table,
+                                            const std::string& table_name) {
   DbSqliteTable::Context context{
       query_cache_.get(), DbSqliteTable::TableComputation::kStatic, &table,
       /*sql_table=*/nullptr, /*generator=*/nullptr};
@@ -127,8 +127,8 @@
   }
 }
 
-void PerfettoSqlEngine::RegisterTableFunction(
-    std::unique_ptr<TableFunction> fn) {
+void PerfettoSqlEngine::RegisterStaticTableFunction(
+    std::unique_ptr<StaticTableFunction> fn) {
   std::string table_name = fn->TableName();
   DbSqliteTable::Context context{
       query_cache_.get(), DbSqliteTable::TableComputation::kTableFunction,
@@ -183,7 +183,7 @@
     } else if (auto* cst = std::get_if<PerfettoSqlParser::CreateTable>(
                    &parser.statement())) {
       RETURN_IF_ERROR(AddTracebackIfNeeded(
-          RegisterSqlTable(cst->name, cst->sql), cst->sql));
+          RegisterRuntimeTable(cst->name, cst->sql), cst->sql));
       // Since the rest of the code requires a statement, just use a no-value
       // dummy statement.
       source = cst->sql.FullRewrite(
@@ -298,8 +298,8 @@
       std::move(*opt_return_type), std::move(return_type_str), std::move(sql));
 }
 
-base::Status PerfettoSqlEngine::RegisterSqlTable(std::string name,
-                                                 SqlSource sql) {
+base::Status PerfettoSqlEngine::RegisterRuntimeTable(std::string name,
+                                                     SqlSource sql) {
   auto stmt_or = engine_->PrepareStatement(sql);
   RETURN_IF_ERROR(stmt_or.status());
   SqliteEngine::PreparedStatement stmt = std::move(stmt_or);
@@ -389,7 +389,7 @@
         SqlSource::FromTraceProcessorImplementation("SELECT 0 WHERE 0"));
   }
 
-  CreatedTableFunction::State state{cf.prototype, cf.sql, {}, {}, std::nullopt};
+  RuntimeTableFunction::State state{cf.prototype, cf.sql, {}, {}, std::nullopt};
   base::StringView function_name;
   RETURN_IF_ERROR(
       ParseFunctionName(state.prototype_str.c_str(), function_name));
@@ -480,7 +480,7 @@
 
   std::string fn_name = state.prototype.function_name;
   std::string lower_name = base::ToLower(state.prototype.function_name);
-  if (created_table_function_state_.Find(lower_name)) {
+  if (runtime_table_fn_states_.Find(lower_name)) {
     if (!cf.replace) {
       return base::ErrStatus("Table function named %s already exists",
                              state.prototype.function_name.c_str());
@@ -493,26 +493,27 @@
     RETURN_IF_ERROR(res.status());
   }
 
-  auto it_and_inserted = created_table_function_state_.Insert(
+  auto it_and_inserted = runtime_table_fn_states_.Insert(
       lower_name,
-      std::make_unique<CreatedTableFunction::State>(std::move(state)));
+      std::make_unique<RuntimeTableFunction::State>(std::move(state)));
   PERFETTO_CHECK(it_and_inserted.second);
 
   base::StackString<1024> create(
-      "CREATE VIRTUAL TABLE %s USING created_table_function", fn_name.c_str());
+      "CREATE VIRTUAL TABLE %s USING runtime_table_function", fn_name.c_str());
   return cf.sql.FullRewrite(
       SqlSource::FromTraceProcessorImplementation(create.ToStdString()));
 }
 
-CreatedTableFunction::State* PerfettoSqlEngine::GetTableFunctionState(
+RuntimeTableFunction::State* PerfettoSqlEngine::GetRuntimeTableFunctionState(
     const std::string& name) const {
-  auto it = created_table_function_state_.Find(base::ToLower(name));
+  auto it = runtime_table_fn_states_.Find(base::ToLower(name));
   PERFETTO_CHECK(it);
   return it->get();
 }
 
-void PerfettoSqlEngine::OnTableFunctionDestroyed(const std::string& name) {
-  PERFETTO_CHECK(created_table_function_state_.Erase(base::ToLower(name)));
+void PerfettoSqlEngine::OnRuntimeTableFunctionDestroyed(
+    const std::string& name) {
+  PERFETTO_CHECK(runtime_table_fn_states_.Erase(base::ToLower(name)));
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
index 9a9c3aa..4cf867a 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -23,10 +23,10 @@
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/status_or.h"
-#include "src/trace_processor/perfetto_sql/engine/created_table_function.h"
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h"
+#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/sql_function.h"
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_source.h"
 #include "src/trace_processor/sqlite/sqlite_engine.h"
@@ -113,16 +113,17 @@
 
   // Registers a trace processor C++ table with SQLite with an SQL name of
   // |name|.
-  void RegisterTable(const Table& table, const std::string& name);
+  void RegisterStaticTable(const Table& table, const std::string& name);
 
   // Registers a trace processor C++ table function with SQLite.
-  void RegisterTableFunction(std::unique_ptr<TableFunction> fn);
+  void RegisterStaticTableFunction(std::unique_ptr<StaticTableFunction> fn);
 
   // Returns the state for the given table function.
-  CreatedTableFunction::State* GetTableFunctionState(const std::string&) const;
+  RuntimeTableFunction::State* GetRuntimeTableFunctionState(
+      const std::string&) const;
 
   // Should be called when a table function is destroyed.
-  void OnTableFunctionDestroyed(const std::string&);
+  void OnRuntimeTableFunctionDestroyed(const std::string&);
 
   SqliteEngine* sqlite_engine() { return engine_.get(); }
 
@@ -131,12 +132,12 @@
       const PerfettoSqlParser::CreateFunction&);
 
   // Registers a SQL-defined trace processor C++ table with SQLite.
-  base::Status RegisterSqlTable(std::string name, SqlSource sql);
+  base::Status RegisterRuntimeTable(std::string name, SqlSource sql);
 
   std::unique_ptr<QueryCache> query_cache_;
   StringPool* pool_ = nullptr;
-  base::FlatHashMap<std::string, std::unique_ptr<CreatedTableFunction::State>>
-      created_table_function_state_;
+  base::FlatHashMap<std::string, std::unique_ptr<RuntimeTableFunction::State>>
+      runtime_table_fn_states_;
   std::unique_ptr<SqliteEngine> engine_;
 };
 
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
index d820da6..459c6e0 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
@@ -17,6 +17,7 @@
 #include "src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h"
 
 #include <algorithm>
+#include <optional>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
@@ -62,13 +63,13 @@
 }  // namespace
 
 PerfettoSqlParser::PerfettoSqlParser(SqlSource sql)
-    : sql_(std::move(sql)), tokenizer_(sql_.sql().c_str()) {}
+    : tokenizer_(std::move(sql)) {}
 
 bool PerfettoSqlParser::Next() {
   PERFETTO_DCHECK(status_.ok());
 
   State state = State::kStmtStart;
-  const char* non_space_ptr = nullptr;
+  std::optional<Token> first_non_space_token;
   for (Token token = tokenizer_.Next();; token = tokenizer_.Next()) {
     // Space should always be completely ignored by any logic below as it will
     // never change the current state in the state machine.
@@ -79,13 +80,9 @@
     if (token.IsTerminal()) {
       // If we have a non-space character we've seen, just return all the stuff
       // after that point.
-      if (non_space_ptr) {
-        uint32_t offset_of_non_space =
-            static_cast<uint32_t>(non_space_ptr - sql_.sql().c_str());
-        uint32_t chars_since_non_space =
-            static_cast<uint32_t>(tokenizer_.ptr() - non_space_ptr);
+      if (first_non_space_token) {
         statement_ =
-            SqliteSql{sql_.Substr(offset_of_non_space, chars_since_non_space)};
+            SqliteSql{tokenizer_.Substr(*first_non_space_token, token)};
         return true;
       }
       // This means we've seen a semi-colon without any non-space content. Just
@@ -99,8 +96,8 @@
     }
 
     // If we've not seen a space character, keep track of the current position.
-    if (!non_space_ptr) {
-      non_space_ptr = token.str.data();
+    if (!first_non_space_token) {
+      first_non_space_token = token;
     }
 
     switch (state) {
@@ -172,15 +169,9 @@
     return ErrorAtToken(token, err.c_str());
   }
 
-  Token tok = tokenizer_.NextNonWhitespace();
-  Token first = tok;
-
-  tok = tokenizer_.NextTerminal();
-
-  uint32_t offset = static_cast<uint32_t>(first.str.data() - sql_.sql().data());
-  uint32_t len = static_cast<uint32_t>(tok.str.end() - sql_.sql().data());
-
-  statement_ = CreateTable{std::move(name), sql_.Substr(offset, len)};
+  Token first = tokenizer_.NextNonWhitespace();
+  statement_ = CreateTable{std::move(name),
+                           tokenizer_.Substr(first, tokenizer_.NextTerminal())};
   return true;
 }
 
@@ -243,15 +234,9 @@
   }
 
   Token first = tokenizer_.NextNonWhitespace();
-  Token token = first;
-  token = tokenizer_.NextTerminal();
-
-  uint32_t offset = static_cast<uint32_t>(first.str.data() - sql_.sql().data());
-  uint32_t len = static_cast<uint32_t>((token.str.data() + token.str.size()) -
-                                       first.str.data());
-
-  statement_ = CreateFunction{replace, std::move(prototype), std::move(ret),
-                              sql_.Substr(offset, len), table_return};
+  statement_ = CreateFunction{
+      replace, std::move(prototype), std::move(ret),
+      tokenizer_.Substr(first, tokenizer_.NextTerminal()), table_return};
   return true;
 }
 
@@ -282,8 +267,7 @@
 
 bool PerfettoSqlParser::ErrorAtToken(const SqliteTokenizer::Token& token,
                                      const char* error) {
-  uint32_t offset = static_cast<uint32_t>(token.str.data() - sql_.sql().data());
-  std::string traceback = sql_.AsTraceback(offset);
+  std::string traceback = tokenizer_.AsTraceback(token);
   status_ = base::ErrStatus("%s%s", traceback.c_str(), error);
   return false;
 }
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
index 6631d38..52bf68f 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
@@ -97,7 +97,6 @@
 
   bool ErrorAtToken(const SqliteTokenizer::Token&, const char* error);
 
-  SqlSource sql_;
   SqliteTokenizer tokenizer_;
   base::Status status_;
   std::optional<Statement> statement_;
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc
index 555f25a..e5eb2f6 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser_unittest.cc
@@ -81,19 +81,24 @@
 
 TEST_F(PerfettoSqlParserTest, SemiColonTerminatedStatement) {
   auto res = SqlSource::FromExecuteQuery("SELECT * FROM slice;");
-  ASSERT_THAT(*Parse(res), testing::ElementsAre(SqliteSql{res}));
+  ASSERT_THAT(
+      *Parse(res),
+      testing::ElementsAre(SqliteSql{FindSubstr(res, "SELECT * FROM slice")}));
 }
 
 TEST_F(PerfettoSqlParserTest, MultipleStmts) {
   auto res =
       SqlSource::FromExecuteQuery("SELECT * FROM slice; SELECT * FROM s");
-  ASSERT_THAT(*Parse(res), testing::ElementsAre(SqliteSql{res.Substr(0, 20)},
-                                                SqliteSql{res.Substr(21, 15)}));
+  ASSERT_THAT(
+      *Parse(res),
+      testing::ElementsAre(SqliteSql{FindSubstr(res, "SELECT * FROM slice")},
+                           SqliteSql{FindSubstr(res, "SELECT * FROM s")}));
 }
 
 TEST_F(PerfettoSqlParserTest, IgnoreOnlySpace) {
   auto res = SqlSource::FromExecuteQuery(" ; SELECT * FROM s; ; ;");
-  ASSERT_THAT(*Parse(res), testing::ElementsAre(SqliteSql{res.Substr(3, 16)}));
+  ASSERT_THAT(*Parse(res), testing::ElementsAre(
+                               SqliteSql{FindSubstr(res, "SELECT * FROM s")}));
 }
 
 TEST_F(PerfettoSqlParserTest, CreatePerfettoFunctionScalar) {
@@ -135,10 +140,10 @@
 TEST_F(PerfettoSqlParserTest, CreatePerfettoFunctionAndOther) {
   auto res = SqlSource::FromExecuteQuery(
       "create perfetto function foo() returns INT as select 1; select foo()");
-  ASSERT_THAT(*Parse(res), testing::ElementsAre(
-                               CreateFn{false, "foo()", "INT",
-                                        FindSubstr(res, "select 1;"), false},
-                               SqliteSql{FindSubstr(res, "select foo()")}));
+  ASSERT_THAT(*Parse(res),
+              testing::ElementsAre(CreateFn{false, "foo()", "INT",
+                                            FindSubstr(res, "select 1"), false},
+                                   SqliteSql{FindSubstr(res, "select foo()")}));
 }
 
 }  // namespace
diff --git a/src/trace_processor/perfetto_sql/engine/created_table_function.cc b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
similarity index 90%
rename from src/trace_processor/perfetto_sql/engine/created_table_function.cc
rename to src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
index 22ef34a..7804e7a 100644
--- a/src/trace_processor/perfetto_sql/engine/created_table_function.cc
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/perfetto_sql/engine/created_table_function.h"
+#include "src/trace_processor/perfetto_sql/engine/runtime_table_function.h"
 
 #include <optional>
 #include <utility>
@@ -34,24 +34,24 @@
 
 }  // namespace
 
-CreatedTableFunction::CreatedTableFunction(sqlite3*, PerfettoSqlEngine* engine)
+RuntimeTableFunction::RuntimeTableFunction(sqlite3*, PerfettoSqlEngine* engine)
     : engine_(engine) {}
 
-CreatedTableFunction::~CreatedTableFunction() {
-  engine_->OnTableFunctionDestroyed(name());
+RuntimeTableFunction::~RuntimeTableFunction() {
+  engine_->OnRuntimeTableFunctionDestroyed(name());
 }
 
-base::Status CreatedTableFunction::Init(int,
+base::Status RuntimeTableFunction::Init(int,
                                         const char* const*,
                                         Schema* schema) {
-  state_ = engine_->GetTableFunctionState(name());
+  state_ = engine_->GetRuntimeTableFunctionState(name());
 
   // Now we've parsed prototype and return values, create the schema.
   *schema = CreateSchema();
   return base::OkStatus();
 }
 
-SqliteTable::Schema CreatedTableFunction::CreateSchema() {
+SqliteTable::Schema RuntimeTableFunction::CreateSchema() {
   std::vector<Column> columns;
   for (size_t i = 0; i < state_->return_values.size(); ++i) {
     const auto& ret = state_->return_values[i];
@@ -82,11 +82,11 @@
   return SqliteTable::Schema(std::move(columns), std::move(primary_keys));
 }
 
-std::unique_ptr<SqliteTable::BaseCursor> CreatedTableFunction::CreateCursor() {
+std::unique_ptr<SqliteTable::BaseCursor> RuntimeTableFunction::CreateCursor() {
   return std::unique_ptr<Cursor>(new Cursor(this, state_));
 }
 
-int CreatedTableFunction::BestIndex(const QueryConstraints& qc,
+int RuntimeTableFunction::BestIndex(const QueryConstraints& qc,
                                     BestIndexInfo* info) {
   // Only accept constraint sets where every input parameter has a value.
   size_t seen_argument_constraints = 0;
@@ -107,7 +107,7 @@
   return SQLITE_OK;
 }
 
-CreatedTableFunction::Cursor::Cursor(CreatedTableFunction* table, State* state)
+RuntimeTableFunction::Cursor::Cursor(RuntimeTableFunction* table, State* state)
     : SqliteTable::BaseCursor(table), table_(table), state_(state) {
   if (state->reusable_stmt) {
     stmt_ = std::move(state->reusable_stmt);
@@ -116,14 +116,14 @@
   }
 }
 
-CreatedTableFunction::Cursor::~Cursor() {
+RuntimeTableFunction::Cursor::~Cursor() {
   if (return_stmt_to_state_) {
     ResetStatement(stmt_->sqlite_stmt());
     state_->reusable_stmt = std::move(stmt_);
   }
 }
 
-base::Status CreatedTableFunction::Cursor::Filter(const QueryConstraints& qc,
+base::Status RuntimeTableFunction::Cursor::Filter(const QueryConstraints& qc,
                                                   sqlite3_value** argv,
                                                   FilterHistory) {
   PERFETTO_TP_TRACE(metatrace::Category::FUNCTION, "TABLE_FUNCTION_CALL",
@@ -216,17 +216,17 @@
   return Next();
 }
 
-base::Status CreatedTableFunction::Cursor::Next() {
+base::Status RuntimeTableFunction::Cursor::Next() {
   is_eof_ = !stmt_->Step();
   next_call_count_++;
   return stmt_->status();
 }
 
-bool CreatedTableFunction::Cursor::Eof() {
+bool RuntimeTableFunction::Cursor::Eof() {
   return is_eof_;
 }
 
-base::Status CreatedTableFunction::Cursor::Column(sqlite3_context* ctx, int i) {
+base::Status RuntimeTableFunction::Cursor::Column(sqlite3_context* ctx, int i) {
   size_t idx = static_cast<size_t>(i);
   if (state_->IsReturnValueColumn(idx)) {
     sqlite3_result_value(ctx, sqlite3_column_value(stmt_->sqlite_stmt(), i));
diff --git a/src/trace_processor/perfetto_sql/engine/created_table_function.h b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
similarity index 82%
rename from src/trace_processor/perfetto_sql/engine/created_table_function.h
rename to src/trace_processor/perfetto_sql/engine/runtime_table_function.h
index 8ae3ca2..9fe8c6e 100644
--- a/src/trace_processor/perfetto_sql/engine/created_table_function.h
+++ b/src/trace_processor/perfetto_sql/engine/runtime_table_function.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_CREATED_TABLE_FUNCTION_H_
-#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_CREATED_TABLE_FUNCTION_H_
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
 
 #include <optional>
 
@@ -29,11 +29,11 @@
 
 // The implementation of the SqliteTable interface for table functions defined
 // at runtime using SQL.
-class CreatedTableFunction final
-    : public TypedSqliteTable<CreatedTableFunction, PerfettoSqlEngine*> {
+class RuntimeTableFunction final
+    : public TypedSqliteTable<RuntimeTableFunction, PerfettoSqlEngine*> {
  public:
-  // The state of this function. This is separated from |CreatedTableFunction|
-  // because |CreatedTableFunction| is owned by Sqlite while |State| is owned by
+  // The state of this function. This is separated from |RuntimeTableFunction|
+  // because |RuntimeTableFunction| is owned by Sqlite while |State| is owned by
   // PerfettoSqlEngine.
   struct State {
     std::string prototype_str;
@@ -68,7 +68,7 @@
   };
   class Cursor final : public SqliteTable::BaseCursor {
    public:
-    explicit Cursor(CreatedTableFunction* table, State* state);
+    explicit Cursor(RuntimeTableFunction* table, State* state);
     ~Cursor() final;
 
     base::Status Filter(const QueryConstraints& qc,
@@ -79,7 +79,7 @@
     base::Status Column(sqlite3_context* context, int N);
 
    private:
-    CreatedTableFunction* table_ = nullptr;
+    RuntimeTableFunction* table_ = nullptr;
     State* state_ = nullptr;
 
     std::optional<SqliteEngine::PreparedStatement> stmt_;
@@ -89,8 +89,8 @@
     int next_call_count_ = 0;
   };
 
-  CreatedTableFunction(sqlite3*, PerfettoSqlEngine*);
-  ~CreatedTableFunction() final;
+  RuntimeTableFunction(sqlite3*, PerfettoSqlEngine*);
+  ~RuntimeTableFunction() final;
 
   base::Status Init(int argc, const char* const* argv, Schema*) final;
   std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
@@ -106,4 +106,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_CREATED_TABLE_FUNCTION_H_
+#endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_ENGINE_RUNTIME_TABLE_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
index 46e6e68..1375d9e 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/BUILD.gn
@@ -62,8 +62,8 @@
 
 source_set("interface") {
   sources = [
-    "table_function.cc",
-    "table_function.h",
+    "static_table_function.cc",
+    "static_table_function.h",
   ]
   deps = [
     "../../../../../gn:default_deps",
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.h
index a2dfce2..4aa8bd2 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.h
@@ -19,7 +19,7 @@
 
 #include <optional>
 
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
@@ -33,7 +33,7 @@
 // * ancestor_slice_by_stack
 //
 // See docs/analysis/trace-processor for usage.
-class Ancestor : public TableFunction {
+class Ancestor : public StaticTableFunction {
  public:
   enum class Type { kSlice = 1, kStackProfileCallsite = 2, kSliceByStack = 3 };
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.h
index 9206968..6a7a804 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.h
@@ -17,7 +17,7 @@
 #ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_CONNECTED_FLOW_H_
 #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_CONNECTED_FLOW_H_
 
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/tables_py.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
@@ -33,7 +33,7 @@
 // - DIRECTLY_CONNECTED_FLOW
 // - PRECEDING_FLOW
 // - FOLLOWING_FLOW
-class ConnectedFlow : public TableFunction {
+class ConnectedFlow : public StaticTableFunction {
  public:
   enum class Mode {
     // Directly connected slices through the same flow ID given by the trace
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.h
index aa0c220..2f7a870 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.h
@@ -19,7 +19,7 @@
 
 #include <optional>
 
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
@@ -32,7 +32,7 @@
 // * descendant_slice_by_stack
 //
 // See docs/analysis/trace-processor for usage.
-class Descendant : public TableFunction {
+class Descendant : public StaticTableFunction {
  public:
   enum class Type { kSlice = 1, kSliceByStack = 2 };
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.h
index 49878a3..193386b 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.h
@@ -17,7 +17,7 @@
 #ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_EXPERIMENTAL_ANNOTATED_STACK_H_
 #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_EXPERIMENTAL_ANNOTATED_STACK_H_
 
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -29,7 +29,7 @@
 // Given a leaf callsite id, returns the full callstack (including the leaf),
 // with optional (currently Android-specific) annotations. A given callsite will
 // always have the same annotation.
-class ExperimentalAnnotatedStack : public TableFunction {
+class ExperimentalAnnotatedStack : public StaticTableFunction {
  public:
   explicit ExperimentalAnnotatedStack(TraceProcessorContext* context)
       : context_(context) {}
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_counter_dur.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_counter_dur.h
index 038bcb0..045c8e2 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_counter_dur.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_counter_dur.h
@@ -17,13 +17,13 @@
 #ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_EXPERIMENTAL_COUNTER_DUR_H_
 #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_EXPERIMENTAL_COUNTER_DUR_H_
 
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-class ExperimentalCounterDur : public TableFunction {
+class ExperimentalCounterDur : public StaticTableFunction {
  public:
   using CounterTable = tables::CounterTable;
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.h
index 8a4cae5..6bc172f 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.h
@@ -18,7 +18,7 @@
 #define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_EXPERIMENTAL_FLAMEGRAPH_H_
 
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.h"
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
@@ -26,7 +26,7 @@
 
 class TraceProcessorContext;
 
-class ExperimentalFlamegraph : public TableFunction {
+class ExperimentalFlamegraph : public StaticTableFunction {
  public:
   enum class ProfileType { kGraph, kHeapProfile, kPerf };
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.h
index 218923e..e9423ed 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.h
@@ -19,7 +19,7 @@
 
 #include <optional>
 
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
@@ -53,7 +53,7 @@
 // (which picks all slices with ts + dur >= bound) and is more akin to doing
 // a simple ts >= bound. However, slices *will* be truncated at the end
 // if they would spill past the provided end bound.
-class ExperimentalFlatSlice : public TableFunction {
+class ExperimentalFlatSlice : public StaticTableFunction {
  public:
   ExperimentalFlatSlice(TraceProcessorContext* context);
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.h
index 800e933..41ff35c 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.h
@@ -19,13 +19,13 @@
 
 #include <set>
 
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-class ExperimentalSchedUpid : public TableFunction {
+class ExperimentalSchedUpid : public StaticTableFunction {
  public:
   ExperimentalSchedUpid(const tables::SchedSliceTable&,
                         const tables::ThreadTable&);
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h
index d23c628..9fcd299 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h
@@ -19,13 +19,13 @@
 
 #include <set>
 
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-class ExperimentalSliceLayout : public TableFunction {
+class ExperimentalSliceLayout : public StaticTableFunction {
  public:
   ExperimentalSliceLayout(StringPool* string_pool,
                           const tables::SliceTable* table);
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.cc
similarity index 90%
rename from src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.cc
rename to src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.cc
index 770f51f..0799e13 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.cc
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-TableFunction::~TableFunction() = default;
+StaticTableFunction::~StaticTableFunction() = default;
 
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h
similarity index 94%
rename from src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h
rename to src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h
index 4079603..75b6c79 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_TABLE_FUNCTION_H_
-#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_TABLE_FUNCTION_H_
+#ifndef SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_STATIC_TABLE_FUNCTION_H_
+#define SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_STATIC_TABLE_FUNCTION_H_
 
 #include "perfetto/base/status.h"
 #include "src/trace_processor/db/table.h"
@@ -28,9 +28,9 @@
 // at filter time.
 // This class is used to implement table-valued functions and other similar
 // tables.
-class TableFunction {
+class StaticTableFunction {
  public:
-  virtual ~TableFunction();
+  virtual ~StaticTableFunction();
 
   // Returns the schema of the table that will be returned by ComputeTable.
   virtual Table::Schema CreateSchema() = 0;
@@ -62,4 +62,4 @@
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_TABLE_FUNCTION_H_
+#endif  // SRC_TRACE_PROCESSOR_PERFETTO_SQL_INTRINSICS_TABLE_FUNCTIONS_STATIC_TABLE_FUNCTION_H_
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/view.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/view.cc
index fca72a4..1055357 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/view.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/view.cc
@@ -19,32 +19,35 @@
 namespace perfetto {
 namespace trace_processor {
 
-ViewTableFunction::ViewTableFunction(const View* view, const char* name)
+ViewStaticTableFunction::ViewStaticTableFunction(const View* view,
+                                                 const char* name)
     : view_(view), name_(name) {}
 
-ViewTableFunction::~ViewTableFunction() = default;
+ViewStaticTableFunction::~ViewStaticTableFunction() = default;
 
-base::Status ViewTableFunction::ValidateConstraints(const QueryConstraints&) {
+base::Status ViewStaticTableFunction::ValidateConstraints(
+    const QueryConstraints&) {
   return base::OkStatus();
 }
 
-base::Status ViewTableFunction::ComputeTable(const std::vector<Constraint>& cs,
-                                             const std::vector<Order>& ob,
-                                             const BitVector& cols_used,
-                                             std::unique_ptr<Table>& table) {
+base::Status ViewStaticTableFunction::ComputeTable(
+    const std::vector<Constraint>& cs,
+    const std::vector<Order>& ob,
+    const BitVector& cols_used,
+    std::unique_ptr<Table>& table) {
   table.reset(new Table(view_->Query(cs, ob, cols_used)));
   return base::OkStatus();
 }
 
-Table::Schema ViewTableFunction::CreateSchema() {
+Table::Schema ViewStaticTableFunction::CreateSchema() {
   return view_->schema();
 }
 
-std::string ViewTableFunction::TableName() {
+std::string ViewStaticTableFunction::TableName() {
   return name_;
 }
 
-uint32_t ViewTableFunction::EstimateRowCount() {
+uint32_t ViewStaticTableFunction::EstimateRowCount() {
   return view_->EstimateRowCount();
 }
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/view.h b/src/trace_processor/perfetto_sql/intrinsics/table_functions/view.h
index 79b3a5c..0bb4648 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/view.h
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/view.h
@@ -19,16 +19,16 @@
 
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "src/trace_processor/db/view.h"
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-class ViewTableFunction : public TableFunction {
+class ViewStaticTableFunction : public StaticTableFunction {
  public:
-  explicit ViewTableFunction(const View*, const char* name);
-  ~ViewTableFunction() override;
+  explicit ViewStaticTableFunction(const View*, const char* name);
+  ~ViewStaticTableFunction() override;
 
   Table::Schema CreateSchema() override;
   std::string TableName() override;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
index 0b95ee1..f0a1a66 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
@@ -418,3 +418,82 @@
 SELECT *, 1 AS is_sync FROM android_sync_binder_metrics_by_txn
 UNION ALL
 SELECT *, 0 AS is_sync FROM android_async_binder_metrics_by_txn;
+
+-- Returns a DAG of all outgoing binder txns from a process.
+-- The roots of the graph are the threads making the txns and the graph flows from:
+-- thread -> server_process -> AIDL interface -> AIDL method.
+-- The weights of each node represent the wall execution time in the server_process.
+--
+-- @arg upid STRING   Upid of process to generate an outgoing graph for.
+-- @ret pprof BYTES   Pprof of outgoing binder txns.
+CREATE PERFETTO FUNCTION ANDROID_BINDER_OUTGOING_GRAPH(upid INT)
+RETURNS TABLE(pprof BYTES) AS
+WITH threads AS (
+  SELECT binder_txn_id, CAT_STACKS(client_thread) AS stack
+  FROM android_binder_txns
+  WHERE ($upid IS NOT NULL AND client_upid = $upid) OR ($upid IS NULL)
+), server_process AS (
+  SELECT binder_txn_id, CAT_STACKS(stack, server_process) AS stack
+  FROM android_binder_txns
+  JOIN threads USING(binder_txn_id)
+), end_points AS (
+  SELECT binder_txn_id,
+         CAT_STACKS(stack, STR_SPLIT(aidl_name, '::', IIF(aidl_name GLOB 'AIDL*', 2, 1))) AS stack
+  FROM android_binder_txns
+  JOIN server_process USING(binder_txn_id)
+), aidl_names AS (
+  SELECT binder_txn_id, server_dur,
+         CAT_STACKS(stack, STR_SPLIT(aidl_name, '::', IIF(aidl_name GLOB 'AIDL*', 3, 2))) AS stack
+  FROM android_binder_txns
+  JOIN end_points USING(binder_txn_id)
+) SELECT EXPERIMENTAL_PROFILE(stack, 'duration', 'ns', server_dur) AS pprof
+  FROM aidl_names;
+
+-- Returns a DAG of all incoming binder txns from a process.
+-- The roots of the graph are the clients making the txns and the graph flows from:
+-- client_process -> AIDL interface -> AIDL method.
+-- The weights of each node represent the wall execution time in the server_process.
+--
+-- @arg upid STRING   Upid of process to generate an outgoing graph for.
+-- @ret pprof BYTES   Pprof of outgoing binder txns.
+CREATE PERFETTO FUNCTION ANDROID_BINDER_INCOMING_GRAPH(upid INT)
+RETURNS TABLE(pprof BYTES) AS
+WITH client_process AS (
+  SELECT binder_txn_id, CAT_STACKS(client_process) AS stack
+  FROM android_binder_txns
+  WHERE ($upid IS NOT NULL AND server_upid = $upid) OR ($upid IS NULL)
+), end_points AS (
+  SELECT binder_txn_id,
+         CAT_STACKS(stack, STR_SPLIT(aidl_name, '::', IIF(aidl_name GLOB 'AIDL*', 2, 1))) AS stack
+  FROM android_binder_txns
+  JOIN client_process USING(binder_txn_id)
+), aidl_names AS (
+  SELECT binder_txn_id, server_dur,
+         CAT_STACKS(stack, STR_SPLIT(aidl_name, '::', IIF(aidl_name GLOB 'AIDL*', 3, 2))) AS stack
+  FROM android_binder_txns
+  JOIN end_points USING(binder_txn_id)
+) SELECT EXPERIMENTAL_PROFILE(stack, 'duration', 'ns', server_dur) AS pprof
+  FROM aidl_names;
+
+-- Returns a graph of all binder txns in a trace.
+-- The nodes are client_process and server_process.
+-- The weights of each node represent the wall execution time in the server_process.
+--
+-- @arg min_client_oom_score INT   Matches txns from client_processes greater than or equal to the OOM score.
+-- @arg max_client_oom_score INT   Matches txns from client_processes less than or equal to the OOM score.
+-- @arg min_server_oom_score INT   Matches txns to server_processes greater than or equal to the OOM score.
+-- @arg max_server_oom_score INT   Matches txns to server_processes less than or equal to the OOM score.
+-- @ret pprof BYTES                Pprof of binder txns.
+CREATE PERFETTO FUNCTION ANDROID_BINDER_GRAPH(min_client_oom_score INT, max_client_oom_score INT, min_server_oom_score INT, max_server_oom_score INT)
+RETURNS TABLE(pprof BYTES) AS
+WITH clients AS (
+  SELECT binder_txn_id, CAT_STACKS(client_process) AS stack
+   FROM android_binder_txns
+   WHERE client_oom_score BETWEEN $min_client_oom_score AND $max_client_oom_score
+), servers AS (
+  SELECT binder_txn_id, server_dur, CAT_STACKS(stack, server_process) AS stack
+  FROM android_binder_txns
+  JOIN clients USING(binder_txn_id)
+  WHERE server_oom_score BETWEEN $min_server_oom_score AND $max_server_oom_score
+) SELECT EXPERIMENTAL_PROFILE(stack, 'duration', 'ns', server_dur) AS pprof
+  FROM servers;
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
index 5baf1b3..10727b5 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql
@@ -370,3 +370,40 @@
 FROM android_monitor_contention_chain_thread_state
 WHERE blocked_function IS NOT NULL
 GROUP BY id, blocked_function;
+
+-- Returns a DAG of all Java lock contentions in a process.
+-- Each node in the graph is a <thread:Java method> pair.
+-- Each edge connects from a node waiting on a lock to a node holding a lock.
+-- The weights of each node represent the cumulative wall time the node blocked
+-- other nodes connected to it.
+--
+-- @arg upid INT      Upid of process to generate a lock graph for.
+-- @ret pprof BYTES   Pprof of lock graph.
+CREATE PERFETTO FUNCTION android_monitor_contention_graph(upid INT)
+RETURNS TABLE(pprof BYTES) AS
+WITH contention_chain AS (
+SELECT *,
+       IIF(blocked_thread_name LIKE 'binder:%', 'binder', blocked_thread_name)
+        AS blocked_thread_name_norm,
+       IIF(blocking_thread_name LIKE 'binder:%', 'binder', blocking_thread_name)
+        AS blocking_thread_name_norm
+FROM android_monitor_contention_chain WHERE upid = $upid
+GROUP BY id, parent_id
+), graph AS (
+SELECT
+  id,
+  dur,
+  CAT_STACKS(blocked_thread_name_norm || ':' || short_blocked_method,
+    blocking_thread_name_norm || ':' || short_blocking_method) AS stack
+FROM contention_chain
+WHERE parent_id IS NULL
+UNION ALL
+SELECT
+c.id,
+c.dur AS dur,
+  CAT_STACKS(blocked_thread_name_norm || ':' || short_blocked_method,
+             blocking_thread_name_norm || ':' || short_blocking_method, stack) AS stack
+FROM contention_chain c, graph AS p
+WHERE p.id = c.parent_id
+) SELECT EXPERIMENTAL_PROFILE(stack, 'duration', 'ns', dur) AS pprof
+  FROM graph;
diff --git a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
index 7bb1344..93cb24d 100644
--- a/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/experimental/thread_executing_span.sql
@@ -492,7 +492,7 @@
 CREATE PERFETTO FUNCTION
 experimental_thread_executing_span_id_from_thread_state_id(thread_state_id INT)
 RETURNS INT AS
-WITH t AS (
+WITH executing AS (
   SELECT
     ts,
     utid
@@ -502,5 +502,140 @@
 )
 SELECT
   MAX(start_id) AS thread_executing_span_id
-FROM internal_wakeup w, t
-WHERE t.utid = w.utid AND t.ts >= w.start_ts AND t.ts < w.end_ts;
+FROM internal_wakeup wakeup, executing
+WHERE executing.utid = wakeup.utid AND executing.ts >= wakeup.start_ts AND executing.ts < wakeup.end_ts;
+
+-- Gets the next thread_executing_span id after a sleeping state. Returns NULL if there is no
+-- thread_executing_span after the |thread_state_id|.
+--
+-- @arg thread_state_id INT   Id of the thread_state to get the next thread_executing_span id for
+-- @ret INT                   thread_executing_span id
+CREATE PERFETTO FUNCTION
+experimental_thread_executing_span_following_thread_state_id(thread_state_id INT)
+RETURNS INT AS
+WITH
+  sleeping AS (
+  SELECT
+    ts,
+    utid
+  FROM thread_state
+  WHERE
+    id = $thread_state_id AND (state = 'S' OR state = 'D' OR state = 'I')
+  )
+SELECT MIN(start_id) AS thread_executing_span_id
+FROM internal_wakeup wakeup, sleeping
+WHERE sleeping.utid = wakeup.utid AND sleeping.ts < wakeup.start_ts;
+
+-- Computes the start and end of each thread_executing_span in the critical path.
+
+-- For the ends, it is the MIN between the end of the current span and the start of
+-- the next span in the critical path.
+
+-- For the starts, it is the MAX between the start of the critical span and the start
+-- of the blocked region. This ensures that the critical path doesn't overlap regions
+-- that are not actually blocked.
+CREATE PERFETTO FUNCTION internal_compute_critical_path_boundaries(thread_executing_span_id INT)
+RETURNS TABLE(id INT, ts LONG, dur LONG) AS
+SELECT
+  id,
+  MAX(ts, leaf_ts - leaf_blocked_dur) AS ts,
+  MIN(
+    MAX(ts, leaf_ts - leaf_blocked_dur) + dur,
+    IFNULL(LEAD(ts) OVER (PARTITION BY leaf_id ORDER BY height DESC), trace_bounds.end_ts))
+    - MAX(ts, leaf_ts - leaf_blocked_dur) AS dur
+FROM EXPERIMENTAL_THREAD_EXECUTING_SPAN_ANCESTORS($thread_executing_span_id), trace_bounds;
+
+-- Critical path of thread_executing_spans blocking the thread_executing_span with id,
+-- |thread_executing_span_id|. For a given thread state span, its duration in the critical path
+-- is the range between the start of the thread_executing_span and the start of the next span in the
+-- critical path.
+--
+-- @arg thread_executing_span_id INT        Id of blocked thread_executing_span.
+--
+-- @column parent_id                        Id of thread_executing_span that directly woke |id|.
+-- @column id                               Id of the first (runnable) thread state in thread_executing_span.
+-- @column ts                               Timestamp of first thread_state in thread_executing_span.
+-- @column dur                              Duration of thread_executing_span within the critical path.
+-- @column tid                              Tid of thread with thread_state.
+-- @column pid                              Pid of process with thread_state.
+-- @column utid                             Utid of thread with thread_state.
+-- @column upid                             Upid of process with thread_state.
+-- @column thread_name                      Name of thread with thread_state.
+-- @column process_name                     Name of process with thread_state.
+-- @column waker_tid                        Tid of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_pid                        Pid of process that woke the first thread_state in thread_executing_span.
+-- @column waker_utid                       Utid of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_upid                       Upid of process that woke the first thread_state in thread_executing_span.
+-- @column waker_thread_name                Name of thread that woke the first thread_state in thread_executing_span.
+-- @column waker_process_name               Name of process that woke the first thread_state in thread_executing_span.
+-- @column blocked_dur                      Duration of blocking thread state before waking up.
+-- @column blocked_state                    Thread state ('D' or 'S') of blocked thread_state before waking up.
+-- @column blocked_function                 Kernel blocking function of thread state before waking up.
+-- @column is_root                          Whether this span is the root in the slice tree.
+-- @column is_leaf                          Whether this span is the leaf in the slice tree.
+-- @column height                           Tree height from |leaf_id|.
+-- @column leaf_id                          Thread state id used to start the recursion. Helpful for SQL JOINs.
+-- @column leaf_ts                          Thread state timestamp of the |leaf_id|.
+-- @column leaf_blocked_dur                 Thread state duration blocked of the |leaf_id|.
+-- @column leaf_blocked_state               Thread state of the |leaf_id|.
+-- @column leaf_blocked_function            Thread state blocked_function of the |leaf_id|.
+CREATE PERFETTO FUNCTION experimental_thread_executing_span_critical_path(thread_executing_span_id INT)
+RETURNS TABLE(
+  parent_id LONG,
+  id LONG,
+  ts LONG,
+  dur LONG,
+  tid INT,
+  pid INT,
+  utid INT,
+  upid INT,
+  thread_name STRING,
+  process_name STRING,
+  waker_tid INT,
+  waker_pid INT,
+  waker_utid INT,
+  waker_upid INT,
+  waker_thread_name STRING,
+  waker_process_name STRING,
+  blocked_dur LONG,
+  blocked_state STRING,
+  blocked_function STRING,
+  is_root INT,
+  is_leaf INT,
+  height INT,
+  leaf_id INT,
+  leaf_ts LONG,
+  leaf_blocked_dur LONG,
+  leaf_blocked_state STRING,
+  leaf_blocked_function STRING
+) AS
+ SELECT
+    parent_id,
+    id,
+    boundary.ts,
+    boundary.dur,
+    tid,
+    pid,
+    utid,
+    upid,
+    thread_name,
+    process_name,
+    waker_tid,
+    waker_pid,
+    waker_utid,
+    waker_upid,
+    waker_thread_name,
+    waker_process_name,
+    blocked_dur,
+    blocked_state,
+    blocked_function,
+    is_root,
+    is_leaf,
+    height,
+    leaf_id,
+    leaf_ts,
+    leaf_blocked_dur,
+    leaf_blocked_state,
+    leaf_blocked_function
+  FROM experimental_thread_executing_span_ancestors($thread_executing_span_id)
+  JOIN internal_compute_critical_path_boundaries($thread_executing_span_id) boundary USING(id);
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index e9c793d..940a65d 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -21,7 +21,7 @@
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/db/runtime_table.h"
 #include "src/trace_processor/db/table.h"
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/sqlite/query_cache.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 
@@ -29,14 +29,13 @@
 namespace trace_processor {
 
 enum class DbSqliteTableComputation {
-  // Mode when the table is static (i.e. passed in at construction
-  // time).
+  // Table is statically defined.
   kStatic,
 
-  // Mode when table is dynamically computed at filter time.
+  // Table is defined as a function.
   kTableFunction,
 
-  // Mode when table is dynamically computer at SQL runtime.
+  // Table is defined in runtime.
   kRuntime
 };
 
@@ -47,11 +46,11 @@
   // Only valid when computation == TableComputation::kStatic.
   const Table* static_table;
 
-  // Only valid when computation == TableComputation::kSql.
+  // Only valid when computation == TableComputation::kRuntime.
   std::unique_ptr<RuntimeTable> sql_table;
 
-  // Only valid when computation == TableComputation::kDynamic.
-  std::unique_ptr<TableFunction> generator;
+  // Only valid when computation == TableComputation::kTableFunction.
+  std::unique_ptr<StaticTableFunction> generator;
 };
 
 // Implements the SQLite table interface for db tables.
@@ -171,8 +170,8 @@
   // Only valid when computation_ == TableComputation::kSql.
   std::unique_ptr<RuntimeTable> sql_table_;
 
-  // Only valid when computation_ == TableComputation::kDynamic.
-  std::unique_ptr<TableFunction> generator_;
+  // Only valid when computation_ == TableComputation::kTableFunction.
+  std::unique_ptr<StaticTableFunction> generator_;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/sqlite/sqlite_table.h b/src/trace_processor/sqlite/sqlite_table.h
index b530870..245fe7c 100644
--- a/src/trace_processor/sqlite/sqlite_table.h
+++ b/src/trace_processor/sqlite/sqlite_table.h
@@ -231,13 +231,12 @@
 
   // This name of the table. For tables created using CREATE VIRTUAL TABLE, this
   // will be the name of the table specified by the query. For automatically
-  // created tables, this will be the same as the module name passed to
-  // RegisterTable.
+  // created tables, this will be the same as the module name registered.
   std::string name_;
 
-  // The module name is the name passed to RegisterTable. This is differs from
-  // the table name (|name_|) where the table was created using CREATE VIRTUAL
-  // TABLE.
+  // The module name is the name that will be registered. This is
+  // differs from the table name (|name_|) where the table was created using
+  // CREATE VIRTUAL TABLE.
   std::string module_name_;
 
   Schema schema_;
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer.cc b/src/trace_processor/sqlite/sqlite_tokenizer.cc
index c582637..6d07c53 100644
--- a/src/trace_processor/sqlite/sqlite_tokenizer.cc
+++ b/src/trace_processor/sqlite/sqlite_tokenizer.cc
@@ -426,13 +426,14 @@
 
 }  // namespace
 
-SqliteTokenizer::SqliteTokenizer(const char* sql) : ptr_(sql) {}
+SqliteTokenizer::SqliteTokenizer(SqlSource sql) : source_(std::move(sql)) {}
 
 SqliteTokenizer::Token SqliteTokenizer::Next() {
   Token token;
-  const char* start = ptr_;
-  int n = GetSqliteToken(unsigned_ptr(), &token.token_type);
-  ptr_ += n;
+  const char* start = source_.sql().data() + offset_;
+  int n = GetSqliteToken(reinterpret_cast<const unsigned char*>(start),
+                         &token.token_type);
+  offset_ += static_cast<uint32_t>(n);
   token.str = std::string_view(start, static_cast<uint32_t>(n));
   return token;
 }
@@ -452,5 +453,18 @@
   return tok;
 }
 
+SqlSource SqliteTokenizer::Substr(Token start, Token end) const {
+  uint32_t offset =
+      static_cast<uint32_t>(start.str.data() - source_.sql().c_str());
+  uint32_t len = static_cast<uint32_t>(end.str.data() - start.str.data());
+  return source_.Substr(offset, len);
+}
+
+std::string SqliteTokenizer::AsTraceback(Token token) const {
+  uint32_t offset =
+      static_cast<uint32_t>(token.str.data() - source_.sql().c_str());
+  return source_.AsTraceback(offset);
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer.h b/src/trace_processor/sqlite/sqlite_tokenizer.h
index 507d46e..c2b4ccc 100644
--- a/src/trace_processor/sqlite/sqlite_tokenizer.h
+++ b/src/trace_processor/sqlite/sqlite_tokenizer.h
@@ -19,6 +19,7 @@
 
 #include <optional>
 #include <string_view>
+#include "src/trace_processor/sqlite/sql_source.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -67,7 +68,7 @@
 // https://www2.sqlite.org/hlr40000.html
 //
 // Usage of this class:
-// SqliteTokenizer tzr(my_sql_string.c_str());
+// SqliteTokenizer tzr(std::move(my_sql_source));
 // for (auto t = tzr.Next(); t.token_type != TK_SEMI; t = tzr.Next()) {
 //   // Handle t here
 // }
@@ -91,7 +92,7 @@
     }
   };
 
-  explicit SqliteTokenizer(const char* sql);
+  explicit SqliteTokenizer(SqlSource sql);
 
   // Returns the next SQL token.
   Token Next();
@@ -102,15 +103,32 @@
   // Returns the next SQL token which is terminal.
   Token NextTerminal();
 
-  // Returns the pointer to the start of the next token which will be returned.
-  const char* ptr() const { return ptr_; }
+  // Returns an SqlSource containing all the tokens between |start| and |end|.
+  //
+  // Note: |start| and |end| must both have been previously returned by this
+  // tokenizer.
+  SqlSource Substr(Token start, Token end) const;
 
- private:
-  const unsigned char* unsigned_ptr() const {
-    return reinterpret_cast<const unsigned char*>(ptr_);
+  // Returns a traceback error message for the SqlSource backing this tokenizer
+  // pointing to |token|. See SqlSource::AsTraceback for more information about
+  // this method.
+  //
+  // Note: |token| must have been previously returned by this tokenizer.
+  std::string AsTraceback(Token) const;
+
+  // Resets this tokenizer to tokenize |source|. Any previous returned tokens
+  // are invalidated.
+  void Reset(SqlSource source) {
+    source_ = std::move(source);
+    offset_ = 0;
   }
 
-  const char* ptr_ = nullptr;
+ private:
+  SqliteTokenizer(SqliteTokenizer&&) = delete;
+  SqliteTokenizer& operator=(SqliteTokenizer&&) = delete;
+
+  SqlSource source_;
+  uint32_t offset_ = 0;
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc b/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc
index 44946b7..e2301fa 100644
--- a/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc
+++ b/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "src/trace_processor/sqlite/sql_source.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
@@ -30,13 +31,16 @@
 class SqliteTokenizerTest : public ::testing::Test {
  protected:
   std::vector<SqliteTokenizer::Token> Tokenize(const char* ptr) {
-    SqliteTokenizer tokenizer(ptr);
+    tokenizer_.Reset(SqlSource::FromTraceProcessorImplementation(ptr));
     std::vector<SqliteTokenizer::Token> tokens;
-    for (auto t = tokenizer.Next(); !t.str.empty(); t = tokenizer.Next()) {
+    for (auto t = tokenizer_.Next(); !t.str.empty(); t = tokenizer_.Next()) {
       tokens.push_back(t);
     }
     return tokens;
   }
+
+ private:
+  SqliteTokenizer tokenizer_{SqlSource::FromTraceProcessorImplementation("")};
 };
 
 TEST_F(SqliteTokenizerTest, EmptyString) {
diff --git a/src/trace_processor/trace_database_integrationtest.cc b/src/trace_processor/trace_database_integrationtest.cc
index 3be0a12..2fd2cdd 100644
--- a/src/trace_processor/trace_database_integrationtest.cc
+++ b/src/trace_processor/trace_database_integrationtest.cc
@@ -512,7 +512,7 @@
   ASSERT_EQ(it.Status().message(),
             R"(Traceback (most recent call last):
   File "stdin" line 1 col 1
-    select RUN_METRIC('foo/bar.sql');
+    select RUN_METRIC('foo/bar.sql')
     ^
   Metric file "foo/bar.sql" line 1 col 8
     select t from slice
@@ -534,7 +534,7 @@
   ASSERT_EQ(it.Status().message(),
             R"(Traceback (most recent call last):
   File "stdin" line 1 col 1
-    select IMPORT('foo.bar');
+    select IMPORT('foo.bar')
     ^
   Module import "foo.bar" line 1 col 8
     select t from slice
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 23e62ef..6a9edd9 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -74,7 +74,7 @@
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_sched_upid.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout.h"
-#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/table_function.h"
+#include "src/trace_processor/perfetto_sql/intrinsics/table_functions/static_table_function.h"
 #include "src/trace_processor/perfetto_sql/intrinsics/table_functions/view.h"
 #include "src/trace_processor/perfetto_sql/prelude/tables_views.h"
 #include "src/trace_processor/perfetto_sql/stdlib/stdlib.h"
@@ -359,8 +359,8 @@
 
 template <typename View>
 void TraceProcessorImpl::RegisterView(const View& view) {
-  RegisterTableFunction(std::unique_ptr<TableFunction>(
-      new ViewTableFunction(&view, View::Name())));
+  RegisterStaticTableFunction(std::unique_ptr<StaticTableFunction>(
+      new ViewStaticTableFunction(&view, View::Name())));
 }
 
 TraceProcessorImpl::TraceProcessorImpl(const Config& cfg)
@@ -500,35 +500,35 @@
       "stats", storage, SqliteTable::TableType::kEponymousOnly, false);
 
   // Tables dynamically generated at query time.
-  RegisterTableFunction(std::unique_ptr<ExperimentalFlamegraph>(
+  RegisterStaticTableFunction(std::unique_ptr<ExperimentalFlamegraph>(
       new ExperimentalFlamegraph(&context_)));
-  RegisterTableFunction(std::unique_ptr<ExperimentalCounterDur>(
+  RegisterStaticTableFunction(std::unique_ptr<ExperimentalCounterDur>(
       new ExperimentalCounterDur(storage->counter_table())));
-  RegisterTableFunction(std::unique_ptr<ExperimentalSliceLayout>(
+  RegisterStaticTableFunction(std::unique_ptr<ExperimentalSliceLayout>(
       new ExperimentalSliceLayout(context_.storage.get()->mutable_string_pool(),
                                   &storage->slice_table())));
-  RegisterTableFunction(std::unique_ptr<Ancestor>(
+  RegisterStaticTableFunction(std::unique_ptr<Ancestor>(
       new Ancestor(Ancestor::Type::kSlice, context_.storage.get())));
-  RegisterTableFunction(std::unique_ptr<Ancestor>(new Ancestor(
+  RegisterStaticTableFunction(std::unique_ptr<Ancestor>(new Ancestor(
       Ancestor::Type::kStackProfileCallsite, context_.storage.get())));
-  RegisterTableFunction(std::unique_ptr<Ancestor>(
+  RegisterStaticTableFunction(std::unique_ptr<Ancestor>(
       new Ancestor(Ancestor::Type::kSliceByStack, context_.storage.get())));
-  RegisterTableFunction(std::unique_ptr<Descendant>(
+  RegisterStaticTableFunction(std::unique_ptr<Descendant>(
       new Descendant(Descendant::Type::kSlice, context_.storage.get())));
-  RegisterTableFunction(std::unique_ptr<Descendant>(
+  RegisterStaticTableFunction(std::unique_ptr<Descendant>(
       new Descendant(Descendant::Type::kSliceByStack, context_.storage.get())));
-  RegisterTableFunction(std::unique_ptr<ConnectedFlow>(new ConnectedFlow(
+  RegisterStaticTableFunction(std::unique_ptr<ConnectedFlow>(new ConnectedFlow(
       ConnectedFlow::Mode::kDirectlyConnectedFlow, context_.storage.get())));
-  RegisterTableFunction(std::unique_ptr<ConnectedFlow>(new ConnectedFlow(
+  RegisterStaticTableFunction(std::unique_ptr<ConnectedFlow>(new ConnectedFlow(
       ConnectedFlow::Mode::kPrecedingFlow, context_.storage.get())));
-  RegisterTableFunction(std::unique_ptr<ConnectedFlow>(new ConnectedFlow(
+  RegisterStaticTableFunction(std::unique_ptr<ConnectedFlow>(new ConnectedFlow(
       ConnectedFlow::Mode::kFollowingFlow, context_.storage.get())));
-  RegisterTableFunction(
+  RegisterStaticTableFunction(
       std::unique_ptr<ExperimentalSchedUpid>(new ExperimentalSchedUpid(
           storage->sched_slice_table(), storage->thread_table())));
-  RegisterTableFunction(std::unique_ptr<ExperimentalAnnotatedStack>(
+  RegisterStaticTableFunction(std::unique_ptr<ExperimentalAnnotatedStack>(
       new ExperimentalAnnotatedStack(&context_)));
-  RegisterTableFunction(std::unique_ptr<ExperimentalFlatSlice>(
+  RegisterStaticTableFunction(std::unique_ptr<ExperimentalFlatSlice>(
       new ExperimentalFlatSlice(&context_)));
 
   // Views.
@@ -538,85 +538,85 @@
   // Note: if adding a table here which might potentially contain many rows
   // (O(rows in sched/slice/counter)), then consider calling ShrinkToFit on
   // that table in TraceStorage::ShrinkToFitTables.
-  RegisterDbTable(storage->arg_table());
-  RegisterDbTable(storage->raw_table());
-  RegisterDbTable(storage->ftrace_event_table());
-  RegisterDbTable(storage->thread_table());
-  RegisterDbTable(storage->process_table());
-  RegisterDbTable(storage->filedescriptor_table());
+  RegisterStaticTable(storage->arg_table());
+  RegisterStaticTable(storage->raw_table());
+  RegisterStaticTable(storage->ftrace_event_table());
+  RegisterStaticTable(storage->thread_table());
+  RegisterStaticTable(storage->process_table());
+  RegisterStaticTable(storage->filedescriptor_table());
 
-  RegisterDbTable(storage->slice_table());
-  RegisterDbTable(storage->flow_table());
-  RegisterDbTable(storage->slice_table());
-  RegisterDbTable(storage->sched_slice_table());
-  RegisterDbTable(storage->spurious_sched_wakeup_table());
-  RegisterDbTable(storage->thread_state_table());
-  RegisterDbTable(storage->gpu_slice_table());
+  RegisterStaticTable(storage->slice_table());
+  RegisterStaticTable(storage->flow_table());
+  RegisterStaticTable(storage->slice_table());
+  RegisterStaticTable(storage->sched_slice_table());
+  RegisterStaticTable(storage->spurious_sched_wakeup_table());
+  RegisterStaticTable(storage->thread_state_table());
+  RegisterStaticTable(storage->gpu_slice_table());
 
-  RegisterDbTable(storage->track_table());
-  RegisterDbTable(storage->thread_track_table());
-  RegisterDbTable(storage->process_track_table());
-  RegisterDbTable(storage->cpu_track_table());
-  RegisterDbTable(storage->gpu_track_table());
+  RegisterStaticTable(storage->track_table());
+  RegisterStaticTable(storage->thread_track_table());
+  RegisterStaticTable(storage->process_track_table());
+  RegisterStaticTable(storage->cpu_track_table());
+  RegisterStaticTable(storage->gpu_track_table());
 
-  RegisterDbTable(storage->counter_table());
+  RegisterStaticTable(storage->counter_table());
 
-  RegisterDbTable(storage->counter_track_table());
-  RegisterDbTable(storage->process_counter_track_table());
-  RegisterDbTable(storage->thread_counter_track_table());
-  RegisterDbTable(storage->cpu_counter_track_table());
-  RegisterDbTable(storage->irq_counter_track_table());
-  RegisterDbTable(storage->softirq_counter_track_table());
-  RegisterDbTable(storage->gpu_counter_track_table());
-  RegisterDbTable(storage->gpu_counter_group_table());
-  RegisterDbTable(storage->perf_counter_track_table());
-  RegisterDbTable(storage->energy_counter_track_table());
-  RegisterDbTable(storage->uid_counter_track_table());
-  RegisterDbTable(storage->energy_per_uid_counter_track_table());
+  RegisterStaticTable(storage->counter_track_table());
+  RegisterStaticTable(storage->process_counter_track_table());
+  RegisterStaticTable(storage->thread_counter_track_table());
+  RegisterStaticTable(storage->cpu_counter_track_table());
+  RegisterStaticTable(storage->irq_counter_track_table());
+  RegisterStaticTable(storage->softirq_counter_track_table());
+  RegisterStaticTable(storage->gpu_counter_track_table());
+  RegisterStaticTable(storage->gpu_counter_group_table());
+  RegisterStaticTable(storage->perf_counter_track_table());
+  RegisterStaticTable(storage->energy_counter_track_table());
+  RegisterStaticTable(storage->uid_counter_track_table());
+  RegisterStaticTable(storage->energy_per_uid_counter_track_table());
 
-  RegisterDbTable(storage->heap_graph_object_table());
-  RegisterDbTable(storage->heap_graph_reference_table());
-  RegisterDbTable(storage->heap_graph_class_table());
+  RegisterStaticTable(storage->heap_graph_object_table());
+  RegisterStaticTable(storage->heap_graph_reference_table());
+  RegisterStaticTable(storage->heap_graph_class_table());
 
-  RegisterDbTable(storage->symbol_table());
-  RegisterDbTable(storage->heap_profile_allocation_table());
-  RegisterDbTable(storage->cpu_profile_stack_sample_table());
-  RegisterDbTable(storage->perf_sample_table());
-  RegisterDbTable(storage->stack_profile_callsite_table());
-  RegisterDbTable(storage->stack_profile_mapping_table());
-  RegisterDbTable(storage->stack_profile_frame_table());
-  RegisterDbTable(storage->package_list_table());
-  RegisterDbTable(storage->profiler_smaps_table());
+  RegisterStaticTable(storage->symbol_table());
+  RegisterStaticTable(storage->heap_profile_allocation_table());
+  RegisterStaticTable(storage->cpu_profile_stack_sample_table());
+  RegisterStaticTable(storage->perf_sample_table());
+  RegisterStaticTable(storage->stack_profile_callsite_table());
+  RegisterStaticTable(storage->stack_profile_mapping_table());
+  RegisterStaticTable(storage->stack_profile_frame_table());
+  RegisterStaticTable(storage->package_list_table());
+  RegisterStaticTable(storage->profiler_smaps_table());
 
-  RegisterDbTable(storage->android_log_table());
-  RegisterDbTable(storage->android_dumpstate_table());
-  RegisterDbTable(storage->android_game_intervention_list_table());
+  RegisterStaticTable(storage->android_log_table());
+  RegisterStaticTable(storage->android_dumpstate_table());
+  RegisterStaticTable(storage->android_game_intervention_list_table());
 
-  RegisterDbTable(storage->vulkan_memory_allocations_table());
+  RegisterStaticTable(storage->vulkan_memory_allocations_table());
 
-  RegisterDbTable(storage->graphics_frame_slice_table());
+  RegisterStaticTable(storage->graphics_frame_slice_table());
 
-  RegisterDbTable(storage->expected_frame_timeline_slice_table());
-  RegisterDbTable(storage->actual_frame_timeline_slice_table());
+  RegisterStaticTable(storage->expected_frame_timeline_slice_table());
+  RegisterStaticTable(storage->actual_frame_timeline_slice_table());
 
-  RegisterDbTable(storage->surfaceflinger_layers_snapshot_table());
-  RegisterDbTable(storage->surfaceflinger_layer_table());
-  RegisterDbTable(storage->surfaceflinger_transactions_table());
+  RegisterStaticTable(storage->surfaceflinger_layers_snapshot_table());
+  RegisterStaticTable(storage->surfaceflinger_layer_table());
+  RegisterStaticTable(storage->surfaceflinger_transactions_table());
 
-  RegisterDbTable(storage->metadata_table());
-  RegisterDbTable(storage->cpu_table());
-  RegisterDbTable(storage->cpu_freq_table());
-  RegisterDbTable(storage->clock_snapshot_table());
+  RegisterStaticTable(storage->metadata_table());
+  RegisterStaticTable(storage->cpu_table());
+  RegisterStaticTable(storage->cpu_freq_table());
+  RegisterStaticTable(storage->clock_snapshot_table());
 
-  RegisterDbTable(storage->memory_snapshot_table());
-  RegisterDbTable(storage->process_memory_snapshot_table());
-  RegisterDbTable(storage->memory_snapshot_node_table());
-  RegisterDbTable(storage->memory_snapshot_edge_table());
+  RegisterStaticTable(storage->memory_snapshot_table());
+  RegisterStaticTable(storage->process_memory_snapshot_table());
+  RegisterStaticTable(storage->memory_snapshot_node_table());
+  RegisterStaticTable(storage->memory_snapshot_edge_table());
 
-  RegisterDbTable(storage->experimental_proto_path_table());
-  RegisterDbTable(storage->experimental_proto_content_table());
+  RegisterStaticTable(storage->experimental_proto_path_table());
+  RegisterStaticTable(storage->experimental_proto_content_table());
 
-  RegisterDbTable(storage->experimental_missing_chrome_processes_table());
+  RegisterStaticTable(storage->experimental_missing_chrome_processes_table());
 }
 
 TraceProcessorImpl::~TraceProcessorImpl() = default;
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 0cf6410..b869a63 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -108,12 +108,12 @@
   friend class IteratorImpl;
 
   template <typename Table>
-  void RegisterDbTable(const Table& table) {
-    engine_.RegisterTable(table, Table::Name());
+  void RegisterStaticTable(const Table& table) {
+    engine_.RegisterStaticTable(table, Table::Name());
   }
 
-  void RegisterTableFunction(std::unique_ptr<TableFunction> fn) {
-    engine_.RegisterTableFunction(std::move(fn));
+  void RegisterStaticTableFunction(std::unique_ptr<StaticTableFunction> fn) {
+    engine_.RegisterStaticTableFunction(std::move(fn));
   }
 
   template <typename View>
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index ba49625..7c0380f 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -401,56 +401,38 @@
 };
 
 base::Status RunMetrics(const std::vector<MetricNameAndPath>& metrics,
-                        OutputFormat format,
-                        const google::protobuf::DescriptorPool& pool) {
+                        OutputFormat format) {
   std::vector<std::string> metric_names(metrics.size());
   for (size_t i = 0; i < metrics.size(); ++i) {
     metric_names[i] = metrics[i].name;
   }
 
-  if (format == OutputFormat::kTextProto) {
-    std::string out;
-    base::Status status =
-        g_tp->ComputeMetricText(metric_names, TraceProcessor::kProtoText, &out);
-    if (!status.ok()) {
-      return status;
-    }
-    out += '\n';
-    fwrite(out.c_str(), sizeof(char), out.size(), stdout);
-    return base::OkStatus();
-  }
-
-  std::vector<uint8_t> metric_result;
-  RETURN_IF_ERROR(g_tp->ComputeMetric(metric_names, &metric_result));
   switch (format) {
-    case OutputFormat::kJson: {
-      // TODO(b/182165266): Handle this using ComputeMetricText.
-      google::protobuf::DynamicMessageFactory factory(&pool);
-      auto* descriptor =
-          pool.FindMessageTypeByName("perfetto.protos.TraceMetrics");
-      std::unique_ptr<google::protobuf::Message> metric_msg(
-          factory.GetPrototype(descriptor)->New());
-      metric_msg->ParseFromArray(metric_result.data(),
-                                 static_cast<int>(metric_result.size()));
-
-      // We need to instantiate field options from dynamic message factory
-      // because otherwise it cannot parse our custom extensions.
-      const google::protobuf::Message* field_options_prototype =
-          factory.GetPrototype(
-              pool.FindMessageTypeByName("google.protobuf.FieldOptions"));
-      auto out = proto_to_json::MessageToJsonWithAnnotations(
-          *metric_msg, field_options_prototype, 0);
-      fwrite(out.c_str(), sizeof(char), out.size(), stdout);
-      break;
-    }
-    case OutputFormat::kBinaryProto:
+    case OutputFormat::kBinaryProto: {
+      std::vector<uint8_t> metric_result;
+      RETURN_IF_ERROR(g_tp->ComputeMetric(metric_names, &metric_result));
       fwrite(metric_result.data(), sizeof(uint8_t), metric_result.size(),
              stdout);
       break;
+    }
+    case OutputFormat::kJson: {
+      std::string out;
+      RETURN_IF_ERROR(g_tp->ComputeMetricText(
+          metric_names, TraceProcessor::MetricResultFormat::kJson, &out));
+      out += '\n';
+      fwrite(out.c_str(), sizeof(char), out.size(), stdout);
+      break;
+    }
+    case OutputFormat::kTextProto: {
+      std::string out;
+      RETURN_IF_ERROR(g_tp->ComputeMetricText(
+          metric_names, TraceProcessor::MetricResultFormat::kProtoText, &out));
+      out += '\n';
+      fwrite(out.c_str(), sizeof(char), out.size(), stdout);
+      break;
+    }
     case OutputFormat::kNone:
       break;
-    case OutputFormat::kTextProto:
-      PERFETTO_FATAL("This case was already handled.");
   }
 
   return base::OkStatus();
@@ -1495,7 +1477,7 @@
         }
 
         base::Status status =
-            RunMetrics(options.metrics, options.metric_format, *options.pool);
+            RunMetrics(options.metrics, options.metric_format);
         if (!status.ok()) {
           fprintf(stderr, "%s\n", status.c_message());
         }
@@ -1660,7 +1642,7 @@
 
   OutputFormat metric_format = ParseOutputFormat(options);
   if (!metrics.empty()) {
-    RETURN_IF_ERROR(RunMetrics(metrics, metric_format, pool));
+    RETURN_IF_ERROR(RunMetrics(metrics, metric_format));
   }
 
   if (!options.query_file_path.empty()) {
diff --git a/test/trace_processor/diff_tests/android/tests.py b/test/trace_processor/diff_tests/android/tests.py
index b035f18..0565554 100644
--- a/test/trace_processor/diff_tests/android/tests.py
+++ b/test/trace_processor/diff_tests/android/tests.py
@@ -14,10 +14,10 @@
 # limitations under the License.
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
-from python.generators.diff_tests.testing import Csv, Json, TextProto
+from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
 from python.generators.diff_tests.testing import DiffTestBlueprint
 from python.generators.diff_tests.testing import TestSuite
-
+from python.generators.diff_tests.testing import PrintProfileProto
 
 class Android(TestSuite):
 
@@ -480,6 +480,37 @@
         query=Metric('android_monitor_contention'),
         out=Path('android_monitor_contention.out'))
 
+  def test_monitor_contention_graph(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_monitor_contention_trace.atr'),
+        query="""
+        SELECT IMPORT('android.monitor_contention');
+
+        SELECT HEX(pprof) FROM android_monitor_contention_graph(303)
+      """,
+      out=BinaryProto(
+        message_type="perfetto.third_party.perftools.profiles.Profile",
+        post_processing=PrintProfileProto,
+        contents="""
+        Sample:
+        Values: 29604
+        Stack:
+        android.bg:android.os.MessageQueue.nativeWake (0x0)
+        fg:android.os.MessageQueue.next (0x0)
+
+        Sample:
+        Values: 66924
+        Stack:
+        android.bg:android.os.MessageQueue.enqueueMessage (0x0)
+        fg:android.os.MessageQueue.next (0x0)
+
+        Sample:
+        Values: 73265
+        Stack:
+        main:android.os.MessageQueue.enqueueMessage (0x0)
+        fg:android.os.MessageQueue.next (0x0)
+        """))
+
   def test_thread_creation_spam(self):
     return DiffTestBlueprint(
         trace=DataPath('android_monitor_contention_trace.atr'),
@@ -685,3 +716,312 @@
         "AIDL::cpp::IInstalld::prepareAppProfile::cppServer","system_server","/system/bin/installd","system_server",641,548,1,-1000,-1000,25281131360,25281145719
         "AIDL::cpp::IInstalld::prepareAppProfile::cppServer","system_server","/system/bin/installd","system_server",641,548,1,-1000,-1000,25281273755,25281315273
       """))
+
+  def test_binder_outgoing_graph(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_binder_metric_trace.atr'),
+        query="""
+        SELECT IMPORT('android.binder');
+        SELECT HEX(pprof) FROM ANDROID_BINDER_OUTGOING_GRAPH(259)
+      """,
+      out=BinaryProto(
+        message_type="perfetto.third_party.perftools.profiles.Profile",
+        post_processing=PrintProfileProto,
+        contents="""
+        Sample:
+        Values: 0
+        Stack:
+        /system/bin/surfaceflinger (0x0)
+        binder:446_1 (0x0)
+
+        Sample:
+        Values: 0
+        Stack:
+        stealReceiveChannel (0x0)
+        IDisplayEventConnection (0x0)
+        /system/bin/surfaceflinger (0x0)
+        binder:446_1 (0x0)
+        """))
+
+  def test_binder_incoming_graph(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_binder_metric_trace.atr'),
+        query="""
+        SELECT IMPORT('android.binder');
+        SELECT HEX(pprof) FROM ANDROID_BINDER_INCOMING_GRAPH(296)
+      """,
+      out=BinaryProto(
+        message_type="perfetto.third_party.perftools.profiles.Profile",
+        post_processing=PrintProfileProto,
+        contents="""
+        Sample:
+        Values: 1764197
+        Stack:
+        fixupAppData (0x0)
+        IInstalld (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 202423
+        Stack:
+        rmdex (0x0)
+        IInstalld (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 438512
+        Stack:
+        cleanupInvalidPackageDirs (0x0)
+        IInstalld (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 4734897
+        Stack:
+        invalidateMounts (0x0)
+        IInstalld (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 7448312
+        Stack:
+        prepareAppProfile (0x0)
+        IInstalld (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 91238713
+        Stack:
+        createAppDataBatched (0x0)
+        IInstalld (0x0)
+        system_server (0x0)
+        """))
+
+  def test_binder_graph_invalid_oom(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_binder_metric_trace.atr'),
+        query="""
+        SELECT IMPORT('android.binder');
+        SELECT HEX(pprof) FROM ANDROID_BINDER_GRAPH(2000, 2000, 2000, 2000)
+      """,
+      out=BinaryProto(
+        message_type="perfetto.third_party.perftools.profiles.Profile",
+        post_processing=PrintProfileProto,
+        contents="""
+        """))
+
+  def test_binder_graph_valid_oom(self):
+    return DiffTestBlueprint(
+        trace=DataPath('android_binder_metric_trace.atr'),
+        query="""
+        SELECT IMPORT('android.binder');
+        SELECT HEX(pprof) FROM ANDROID_BINDER_GRAPH(-1000, 1000, -1000, 1000)
+      """,
+      out=BinaryProto(
+        message_type="perfetto.third_party.perftools.profiles.Profile",
+        post_processing=PrintProfileProto,
+        contents="""
+        Sample:
+        Values: 0
+        Stack:
+        /system/bin/apexd (0x0)
+        /system/bin/servicemanager (0x0)
+
+        Sample:
+        Values: 0
+        Stack:
+        /system/bin/bootanimation (0x0)
+        /system/bin/surfaceflinger (0x0)
+
+        Sample:
+        Values: 0
+        Stack:
+        /system/bin/cameraserver (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 0
+        Stack:
+        /system/bin/storaged (0x0)
+        /vendor/bin/hw/android.hardware.health-service.cuttlefish (0x0)
+
+        Sample:
+        Values: 0
+        Stack:
+        /system/bin/surfaceflinger (0x0)
+        /system/bin/bootanimation (0x0)
+
+        Sample:
+        Values: 0
+        Stack:
+        /system/bin/surfaceflinger (0x0)
+        /vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu (0x0)
+
+        Sample:
+        Values: 0
+        Stack:
+        media.metrics (0x0)
+        /system/bin/audioserver (0x0)
+
+        Sample:
+        Values: 0
+        Stack:
+        system_server (0x0)
+        /system/bin/servicemanager (0x0)
+
+        Sample:
+        Values: 0
+        Stack:
+        system_server (0x0)
+        /system/bin/surfaceflinger (0x0)
+
+        Sample:
+        Values: 105827054
+        Stack:
+        /system/bin/installd (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 11316
+        Stack:
+        system_server (0x0)
+        /apex/com.android.os.statsd/bin/statsd (0x0)
+
+        Sample:
+        Values: 12567639
+        Stack:
+        /system/bin/servicemanager (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 137623
+        Stack:
+        /vendor/bin/hw/android.hardware.lights-service.example (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 140719
+        Stack:
+        system_server (0x0)
+        /system/bin/storaged (0x0)
+
+        Sample:
+        Values: 150044
+        Stack:
+        /vendor/bin/hw/android.hardware.input.processor-service.example (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 1877718
+        Stack:
+        /system/bin/surfaceflinger (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 19303
+        Stack:
+        system_server (0x0)
+        /vendor/bin/hw/android.hardware.sensors-service.example (0x0)
+
+        Sample:
+        Values: 210889
+        Stack:
+        /system/bin/servicemanager (0x0)
+        /apex/com.android.os.statsd/bin/statsd (0x0)
+
+        Sample:
+        Values: 21505514
+        Stack:
+        /system/bin/idmap2d (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 25394
+        Stack:
+        /system/bin/servicemanager (0x0)
+        /system/bin/surfaceflinger (0x0)
+
+        Sample:
+        Values: 2552696
+        Stack:
+        /system/bin/hwservicemanager (0x0)
+        /system/bin/cameraserver (0x0)
+
+        Sample:
+        Values: 273686
+        Stack:
+        /vendor/bin/hw/android.hardware.sensors-service.example (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 28045
+        Stack:
+        /apex/com.android.os.statsd/bin/statsd (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 297647
+        Stack:
+        /system/bin/hwservicemanager (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 3483649
+        Stack:
+        system_server (0x0)
+        /system/bin/audioserver (0x0)
+
+        Sample:
+        Values: 3677545
+        Stack:
+        /system/bin/servicemanager (0x0)
+        /system/bin/audioserver (0x0)
+
+        Sample:
+        Values: 3991341
+        Stack:
+        /system/bin/servicemanager (0x0)
+        /system/bin/cameraserver (0x0)
+
+        Sample:
+        Values: 41164
+        Stack:
+        system_server (0x0)
+        /vendor/bin/hw/android.hardware.health-service.cuttlefish (0x0)
+
+        Sample:
+        Values: 4948091
+        Stack:
+        system_server (0x0)
+        /system/bin/cameraserver (0x0)
+
+        Sample:
+        Values: 502254
+        Stack:
+        /vendor/bin/hw/android.hardware.health-service.cuttlefish (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 629626
+        Stack:
+        /apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 78428525
+        Stack:
+        /vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu (0x0)
+        /system/bin/surfaceflinger (0x0)
+
+        Sample:
+        Values: 81216
+        Stack:
+        /system/bin/vold (0x0)
+        system_server (0x0)
+
+        Sample:
+        Values: 837989
+        Stack:
+        /system/bin/servicemanager (0x0)
+        /system/bin/storaged (0x0)
+        """))
diff --git a/test/trace_processor/diff_tests/functions/tests.py b/test/trace_processor/diff_tests/functions/tests.py
index 25ce166..64adb47 100644
--- a/test/trace_processor/diff_tests/functions/tests.py
+++ b/test/trace_processor/diff_tests/functions/tests.py
@@ -17,27 +17,9 @@
 from python.generators.diff_tests.testing import Csv, Json, TextProto, BinaryProto
 from python.generators.diff_tests.testing import DiffTestBlueprint
 from python.generators.diff_tests.testing import TestSuite
+from python.generators.diff_tests.testing import PrintProfileProto
 from google.protobuf import text_format
 
-
-def PrintProfileProto(profile):
-  locations = {l.id: l for l in profile.location}
-  functions = {f.id: f for f in profile.function}
-  samples = []
-  for s in profile.sample:
-    stack = []
-    for location in [locations[id] for id in s.location_id]:
-      for function in [functions[l.function_id] for l in location.line]:
-        stack.append("{name} ({address})".format(
-            name=profile.string_table[function.name],
-            address=hex(location.address)))
-      if len(location.line) == 0:
-        stack.append("({address})".format(address=hex(location.address)))
-    samples.append('Sample:\nValues: {values}\nStack:\n{stack}'.format(
-        values=', '.join(map(str, s.value)), stack='\n'.join(stack)))
-  return '\n\n'.join(sorted(samples)) + '\n'
-
-
 class Functions(TestSuite):
 
   def test_create_function(self):
diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py
index 92bbe64..00facf8 100644
--- a/test/trace_processor/diff_tests/tables/tests_sched.py
+++ b/test/trace_processor/diff_tests/tables/tests_sched.py
@@ -353,9 +353,75 @@
         trace=DataPath('sched_wakeup_trace.atr'),
         query="""
         SELECT IMPORT('experimental.thread_executing_span');
-        SELECT EXPERIMENTAL_THREAD_EXECUTING_SPAN_ID_FROM_THREAD_STATE_ID(13120) AS thread_executing_span_id
+        SELECT EXPERIMENTAL_THREAD_EXECUTING_SPAN_ID_FROM_THREAD_STATE_ID(15173) AS thread_executing_span_id
         """,
         out=Csv("""
         "thread_executing_span_id"
         "[NULL]"
         """))
+
+  def test_thread_executing_span_following_from_sleep_thread_state(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT EXPERIMENTAL_THREAD_EXECUTING_SPAN_FOLLOWING_THREAD_STATE_ID(15173) AS thread_executing_span_id
+        """,
+        out=Csv("""
+        "thread_executing_span_id"
+        15750
+        """))
+
+  def test_thread_executing_span_following_from_non_sleep_thread_state(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT EXPERIMENTAL_THREAD_EXECUTING_SPAN_FOLLOWING_THREAD_STATE_ID(12394) AS thread_executing_span_id
+        """,
+        out=Csv("""
+        "thread_executing_span_id"
+        "[NULL]"
+        """))
+
+  def test_thread_executing_span_critical_path(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT IMPORT('experimental.thread_executing_span');
+        SELECT
+          ts,
+          dur,
+          tid,
+          pid,
+          thread_name,
+          process_name,
+          waker_thread_name,
+          waker_process_name,
+          blocked_dur,
+          blocked_state,
+          blocked_function,
+          height,
+          is_leaf,
+          leaf_ts,
+          leaf_blocked_dur,
+          leaf_blocked_state,
+          leaf_blocked_function
+        FROM EXPERIMENTAL_THREAD_EXECUTING_SPAN_CRITICAL_PATH(EXPERIMENTAL_THREAD_EXECUTING_SPAN_FOLLOWING_THREAD_STATE_ID(15173))
+        ORDER BY ts
+        """,
+        out=Csv("""
+        "ts","dur","tid","pid","thread_name","process_name","waker_thread_name","waker_process_name","blocked_dur","blocked_state","blocked_function","height","is_leaf","leaf_ts","leaf_blocked_dur","leaf_blocked_state","leaf_blocked_function"
+        1737555644935,155300703,281,243,"binder:243_4","/system/bin/vold","StorageManagerS","system_server",207137317,"S","[NULL]",11,0,1737716642304,160997369,"S","[NULL]"
+        1737710945638,719567,158,1,"init","/system/bin/init","binder:243_4","/system/bin/vold",320099853,"S","[NULL]",10,0,1737716642304,160997369,"S","[NULL]"
+        1737711665205,2066552,281,243,"binder:243_4","/system/bin/vold","init","/system/bin/init",473986,"S","[NULL]",9,0,1737716642304,160997369,"S","[NULL]"
+        1737713731757,46394,3335,3335,"kworker/u4:2","kworker/u4:2-events_unbound","binder:243_4","/system/bin/vold",172402014,"I","worker_thread",8,0,1737716642304,160997369,"S","[NULL]"
+        1737713778151,818659,281,243,"binder:243_4","/system/bin/vold","kworker/u4:2","kworker/u4:2-events_unbound",38815,"D","__flush_work",7,0,1737716642304,160997369,"S","[NULL]"
+        1737714596810,414789,743,642,"StorageManagerS","system_server","binder:243_4","/system/bin/vold",161843036,"S","[NULL]",6,0,1737716642304,160997369,"S","[NULL]"
+        1737715011599,256989,3501,3487,"binder:3487_4","com.android.providers.media.module","StorageManagerS","system_server",167350508,"S","[NULL]",5,0,1737716642304,160997369,"S","[NULL]"
+        1737715268588,219727,3519,3487,"android.bg","com.android.providers.media.module","binder:3487_4","com.android.providers.media.module",163900842,"S","[NULL]",4,0,1737716642304,160997369,"S","[NULL]"
+        1737715488315,357472,657,642,"binder:642_1","system_server","android.bg","com.android.providers.media.module",344488980,"S","[NULL]",3,0,1737716642304,160997369,"S","[NULL]"
+        1737715845787,497587,743,642,"StorageManagerS","system_server","binder:642_1","system_server",793525,"S","[NULL]",2,0,1737716642304,160997369,"S","[NULL]"
+        1737716343374,298930,3501,3487,"binder:3487_4","com.android.providers.media.module","StorageManagerS","system_server",1016895,"S","[NULL]",1,0,1737716642304,160997369,"S","[NULL]"
+        1737716642304,4521857,3487,3487,"rs.media.module","com.android.providers.media.module","binder:3487_4","com.android.providers.media.module",160997369,"S","[NULL]",0,0,1737716642304,160997369,"S","[NULL]"
+        """))
diff --git a/ui/src/base/comparison_utils.ts b/ui/src/base/comparison_utils.ts
index ea6110a..842c812 100644
--- a/ui/src/base/comparison_utils.ts
+++ b/ui/src/base/comparison_utils.ts
@@ -12,8 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {ColumnType} from '../common/query_result';
-
 export type ComparisonFn<X> = (a: X, b: X) => number;
 
 export type SortDirection = 'DESC'|'ASC';
@@ -39,7 +37,7 @@
   };
 }
 
-export type SortableValue = ColumnType|undefined;
+export type SortableValue = string|number|bigint|null|Uint8Array|undefined;
 
 function columnTypeKind(a: SortableValue): number {
   if (a === undefined) {
diff --git a/ui/src/base/json_utils.ts b/ui/src/base/json_utils.ts
new file mode 100644
index 0000000..69d97d0
--- /dev/null
+++ b/ui/src/base/json_utils.ts
@@ -0,0 +1,23 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Similar to JSON.stringify() but supports bigints.
+// Bigints are simply serialized to a string, so the original object cannot be
+// recovered with JSON.parse(), as bigints will turn into strings.
+// Useful for e.g. tracing, where string arg values are required.
+export function stringifyJsonWithBigints(object: any): string {
+  return JSON.stringify(
+      object,
+      (_, value) => typeof value === 'bigint' ? value.toString() : value);
+}
diff --git a/ui/src/base/json_utils_unittest.ts b/ui/src/base/json_utils_unittest.ts
new file mode 100644
index 0000000..540e03c
--- /dev/null
+++ b/ui/src/base/json_utils_unittest.ts
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {stringifyJsonWithBigints} from './json_utils';
+
+test('stringifyJsonWithBigints', () => {
+  const obj = {foo: 'bar', baz: 123n};
+  const expected = '{"foo":"bar","baz":"123"}';
+  expect(stringifyJsonWithBigints(obj)).toEqual(expected);
+});
diff --git a/ui/src/chrome_extension/chrome_tracing_controller.ts b/ui/src/chrome_extension/chrome_tracing_controller.ts
index 42f2a48..9ed3930 100644
--- a/ui/src/chrome_extension/chrome_tracing_controller.ts
+++ b/ui/src/chrome_extension/chrome_tracing_controller.ts
@@ -18,17 +18,17 @@
 
 import {base64Encode} from '../base/string_utils';
 import {
-  browserSupportsPerfettoConfig,
-  extractTraceConfig,
-  hasSystemDataSourceConfig,
-} from '../base/trace_config_utils';
-import {TraceConfig} from '../common/protos';
-import {
   ConsumerPortResponse,
   GetTraceStatsResponse,
   ReadBuffersResponse,
 } from '../controller/consumer_port_types';
 import {RpcConsumerPort} from '../controller/record_controller_interfaces';
+import {TraceConfig} from '../core/protos';
+import {
+  browserSupportsPerfettoConfig,
+  extractTraceConfig,
+  hasSystemDataSourceConfig,
+} from '../core/trace_config_utils';
 import {perfetto} from '../gen/protos';
 
 import {DevToolsSocket} from './devtools_socket';
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index 7bbde09..5546ea2 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -15,16 +15,16 @@
 import {defer, Deferred} from '../base/deferred';
 import {assertExists, assertTrue} from '../base/logging';
 import {Span, Time} from '../common/time';
-import {perfetto} from '../gen/protos';
-
-import {ProtoRingBuffer} from './proto_ring_buffer';
 import {
   ComputeMetricArgs,
   ComputeMetricResult,
   DisableAndReadMetatraceResult,
   QueryArgs,
   ResetTraceProcessorArgs,
-} from './protos';
+} from '../core/protos';
+import {perfetto} from '../gen/protos';
+
+import {ProtoRingBuffer} from './proto_ring_buffer';
 import {
   createQueryResult,
   LONG,
diff --git a/ui/src/common/http_rpc_engine.ts b/ui/src/common/http_rpc_engine.ts
index ddeec7c..f9db060 100644
--- a/ui/src/common/http_rpc_engine.ts
+++ b/ui/src/common/http_rpc_engine.ts
@@ -14,7 +14,7 @@
 
 import {fetchWithTimeout} from '../base/http_utils';
 import {assertExists} from '../base/logging';
-import {StatusResult} from '../common/protos';
+import {StatusResult} from '../core/protos';
 
 import {Engine, LoadingTracker} from './engine';
 
diff --git a/ui/src/common/metatracing.ts b/ui/src/common/metatracing.ts
index 68bec99..99c5ea9 100644
--- a/ui/src/common/metatracing.ts
+++ b/ui/src/common/metatracing.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {PerfettoMetatrace, Trace, TracePacket} from '../common/protos';
+import {PerfettoMetatrace, Trace, TracePacket} from '../core/protos';
 import {perfetto} from '../gen/protos';
 
 import {featureFlags} from './feature_flags';
@@ -163,3 +163,31 @@
     traceEvents.shift();
   }
 }
+
+// Flatten arbitrary values so they can be used as args in traceEvent() et al.
+export function flattenArgs(
+    input: unknown, parentKey = ''): {[key: string]: string} {
+  if (typeof input !== 'object' || input === null) {
+    return {[parentKey]: String(input)};
+  }
+
+  if (Array.isArray(input)) {
+    const result: Record<string, string> = {};
+
+    (input as Array<unknown>).forEach((item, index) => {
+      const arrayKey = `${parentKey}[${index}]`;
+      Object.assign(result, flattenArgs(item, arrayKey));
+    });
+
+    return result;
+  }
+
+  const result: Record<string, string> = {};
+
+  Object.entries(input as Record<string, unknown>).forEach(([key, value]) => {
+    const newKey = parentKey ? `${parentKey}.${key}` : key;
+    Object.assign(result, flattenArgs(value, newKey));
+  });
+
+  return result;
+}
diff --git a/ui/src/common/metatracing_unittest.ts b/ui/src/common/metatracing_unittest.ts
new file mode 100644
index 0000000..215b77d
--- /dev/null
+++ b/ui/src/common/metatracing_unittest.ts
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {flattenArgs} from './metatracing';
+
+describe('flattenArgs', () => {
+  test('flattens nested object', () => {
+    const value = {
+      foo: {
+        bar: [1, 2, 3],
+      },
+      baz: {baz: 'qux'},
+    };
+    expect(flattenArgs(value)).toStrictEqual({
+      'foo.bar[0]': '1',
+      'foo.bar[1]': '2',
+      'foo.bar[2]': '3',
+      'baz.baz': 'qux',
+    });
+  });
+
+  test('flattens single value', () => {
+    const value = 123;
+    expect(flattenArgs(value)).toStrictEqual({
+      '': '123',
+    });
+  });
+
+  test('flattens array', () => {
+    const value = [1, 2, 3];
+    expect(flattenArgs(value)).toStrictEqual({
+      '[0]': '1',
+      '[1]': '2',
+      '[2]': '3',
+    });
+  });
+
+  test('flattens array of objects', () => {
+    const value = [{foo: 'bar'}, {baz: 123}];
+    expect(flattenArgs(value)).toStrictEqual({
+      '[0].foo': 'bar',
+      '[1].baz': '123',
+    });
+  });
+});
diff --git a/ui/src/common/protos_unittest.ts b/ui/src/common/protos_unittest.ts
index 181917a..174e350 100644
--- a/ui/src/common/protos_unittest.ts
+++ b/ui/src/common/protos_unittest.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {TraceConfig} from './protos';
+import {TraceConfig} from '../core/protos';
 
 test('round trip config proto', () => {
   const input = TraceConfig.create({
diff --git a/ui/src/common/recordingV2/chrome_traced_tracing_session.ts b/ui/src/common/recordingV2/chrome_traced_tracing_session.ts
index e36a5aa..a42528b 100644
--- a/ui/src/common/recordingV2/chrome_traced_tracing_session.ts
+++ b/ui/src/common/recordingV2/chrome_traced_tracing_session.ts
@@ -33,7 +33,8 @@
   IBufferStats,
   ISlice,
   TraceConfig,
-} from '../protos';
+} from '../../core/protos';
+
 import {RecordingError} from './recording_error_handling';
 import {
   TracingSession,
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index 139227e..9524670 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -15,7 +15,6 @@
 
 import {base64Encode} from '../../base/string_utils';
 import {RecordConfig} from '../../controller/record_config_types';
-import {perfetto} from '../../gen/protos';
 import {
   AndroidLogConfig,
   AndroidLogId,
@@ -36,7 +35,8 @@
   TraceConfig,
   TrackEventConfig,
   VmstatCounters,
-} from '../protos';
+} from '../../core/protos';
+import {perfetto} from '../../gen/protos';
 
 import {TargetInfo} from './recording_interfaces_v2';
 
diff --git a/ui/src/common/recordingV2/recording_interfaces_v2.ts b/ui/src/common/recordingV2/recording_interfaces_v2.ts
index 974d277..ca187e2 100644
--- a/ui/src/common/recordingV2/recording_interfaces_v2.ts
+++ b/ui/src/common/recordingV2/recording_interfaces_v2.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {TraceConfig} from '../protos';
+import {TraceConfig} from '../../core/protos';
 
 // TargetFactory connects, disconnects and keeps track of targets.
 // There is one factory for AndroidWebusb, AndroidWebsocket, Chrome etc.
diff --git a/ui/src/common/recordingV2/recording_page_controller.ts b/ui/src/common/recordingV2/recording_page_controller.ts
index 438843d..c42130c 100644
--- a/ui/src/common/recordingV2/recording_page_controller.ts
+++ b/ui/src/common/recordingV2/recording_page_controller.ts
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 import {assertExists, assertTrue} from '../../base/logging';
+import {TraceConfig} from '../../core/protos';
 import {raf} from '../../core/raf_scheduler';
 import {globals} from '../../frontend/globals';
 import {autosaveConfigStore} from '../../frontend/record_config';
@@ -25,7 +26,6 @@
 } from '../../frontend/recording/reset_interface_modal';
 import {Actions} from '../actions';
 import {TRACE_SUFFIX} from '../constants';
-import {TraceConfig} from '../protos';
 import {currentDateHourAndMinute} from '../time';
 
 import {genTraceConfig} from './recording_config_utils';
diff --git a/ui/src/common/recordingV2/traced_tracing_session.ts b/ui/src/common/recordingV2/traced_tracing_session.ts
index 0b1f656..a49c780 100644
--- a/ui/src/common/recordingV2/traced_tracing_session.ts
+++ b/ui/src/common/recordingV2/traced_tracing_session.ts
@@ -34,7 +34,7 @@
   ReadBuffersRequest,
   ReadBuffersResponse,
   TraceConfig,
-} from '../protos';
+} from '../../core/protos';
 
 import {RecordingError} from './recording_error_handling';
 import {
diff --git a/ui/src/controller/adb_base_controller.ts b/ui/src/controller/adb_base_controller.ts
index bbafbc3..144614e 100644
--- a/ui/src/controller/adb_base_controller.ts
+++ b/ui/src/controller/adb_base_controller.ts
@@ -12,9 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {extractDurationFromTraceConfig} from '../base/trace_config_utils';
-import {extractTraceConfig} from '../base/trace_config_utils';
 import {isAdbTarget} from '../common/state';
+import {
+  extractDurationFromTraceConfig,
+  extractTraceConfig,
+} from '../core/trace_config_utils';
 import {globals} from '../frontend/globals';
 
 import {Adb} from './adb_interfaces';
diff --git a/ui/src/controller/adb_shell_controller.ts b/ui/src/controller/adb_shell_controller.ts
index 4c7b6a2..d2fe620 100644
--- a/ui/src/controller/adb_shell_controller.ts
+++ b/ui/src/controller/adb_shell_controller.ts
@@ -15,7 +15,7 @@
 import {_TextDecoder} from 'custom_utils';
 
 import {base64Encode} from '../base/string_utils';
-import {extractTraceConfig} from '../base/trace_config_utils';
+import {extractTraceConfig} from '../core/trace_config_utils';
 
 import {AdbBaseConsumerPort, AdbConnectionState} from './adb_base_controller';
 import {Adb, AdbStream} from './adb_interfaces';
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index e8cb6dd..32d5e75 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -17,10 +17,6 @@
 import {base64Encode} from '../base/string_utils';
 import {Actions} from '../common/actions';
 import {TRACE_SUFFIX} from '../common/constants';
-import {
-  ConsumerPort,
-  TraceConfig,
-} from '../common/protos';
 import {genTraceConfig} from '../common/recordingV2/recording_config_utils';
 import {TargetInfo} from '../common/recordingV2/recording_interfaces_v2';
 import {
@@ -29,6 +25,10 @@
   isChromeTarget,
   RecordingTarget,
 } from '../common/state';
+import {
+  ConsumerPort,
+  TraceConfig,
+} from '../core/protos';
 import {globals} from '../frontend/globals';
 import {publishBufferUsage, publishTrackData} from '../frontend/publish';
 
diff --git a/ui/src/controller/record_controller_jsdomtest.ts b/ui/src/controller/record_controller_jsdomtest.ts
index bc7c15b..55411ec 100644
--- a/ui/src/controller/record_controller_jsdomtest.ts
+++ b/ui/src/controller/record_controller_jsdomtest.ts
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 import {assertExists} from '../base/logging';
-import {TraceConfig} from '../common/protos';
+import {TraceConfig} from '../core/protos';
 
 import {createEmptyRecordConfig} from './record_config_types';
 import {genConfigProto, toPbtxt} from './record_controller';
diff --git a/ui/src/common/protos.ts b/ui/src/core/protos.ts
similarity index 99%
rename from ui/src/common/protos.ts
rename to ui/src/core/protos.ts
index fa83fac..c3fdaca 100644
--- a/ui/src/common/protos.ts
+++ b/ui/src/core/protos.ts
@@ -86,12 +86,12 @@
   BatteryCounters,
   BufferConfig,
   ChromeConfig,
-  ConsumerPort,
   ComputeMetricArgs,
   ComputeMetricResult,
+  ConsumerPort,
   DataSourceConfig,
-  DisableAndReadMetatraceResult,
   DataSourceDescriptor,
+  DisableAndReadMetatraceResult,
   DisableTracingRequest,
   DisableTracingResponse,
   EnableTracingRequest,
@@ -116,21 +116,21 @@
   MeminfoCounters,
   NativeContinuousDumpConfig,
   NetworkPacketTraceConfig,
-  ProcessStatsConfig,
   PerfettoMetatrace,
   PerfEventConfig,
-  ReadBuffersRequest,
-  ReadBuffersResponse,
+  ProcessStatsConfig,
+  QueryArgs,
   QueryServiceStateRequest,
   QueryServiceStateResponse,
-  QueryArgs,
+  ReadBuffersRequest,
+  ReadBuffersResponse,
   ResetTraceProcessorArgs,
   StatCounters,
   StatusResult,
   SysStatsConfig,
   Trace,
   TraceConfig,
-  TrackEventConfig,
   TracePacket,
+  TrackEventConfig,
   VmstatCounters,
 };
diff --git a/ui/src/base/trace_config_utils.ts b/ui/src/core/trace_config_utils.ts
similarity index 97%
rename from ui/src/base/trace_config_utils.ts
rename to ui/src/core/trace_config_utils.ts
index 3a08963..e7e6daf 100644
--- a/ui/src/base/trace_config_utils.ts
+++ b/ui/src/core/trace_config_utils.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {TraceConfig} from '../common/protos';
+import {TraceConfig} from '../core/protos';
 import {perfetto} from '../gen/protos';
 
 // In this file are contained a few functions to simplify the proto parsing.
diff --git a/ui/src/frontend/bottom_tab.ts b/ui/src/frontend/bottom_tab.ts
index f063152..5a42b5f 100644
--- a/ui/src/frontend/bottom_tab.ts
+++ b/ui/src/frontend/bottom_tab.ts
@@ -15,6 +15,7 @@
 import m from 'mithril';
 import {v4 as uuidv4} from 'uuid';
 
+import {stringifyJsonWithBigints} from '../base/json_utils';
 import {Actions} from '../common/actions';
 import {EngineProxy} from '../common/engine';
 import {traceEvent} from '../common/metatracing';
@@ -236,9 +237,7 @@
         'uuid': uuid,
         'kind': args.kind,
         'tag': args.tag ?? '<undefined>',
-        'config': JSON.stringify(
-            args.config,
-            (_, value) => typeof value === 'bigint' ? value.toString() : value),
+        'config': stringifyJsonWithBigints(args.config),
       },
     });
   }
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 058633d..2bf320f 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -65,6 +65,10 @@
   return slice.process?.pid;
 }
 
+function getUpidFromSlice(slice: SliceDetails): number|undefined {
+  return slice.process?.upid;
+}
+
 function getProcessNameFromSlice(slice: SliceDetails): string|undefined {
   return slice.process?.name;
 }
@@ -139,44 +143,11 @@
     name: 'Lock graph',
     shouldDisplay: (slice: SliceDetails) => hasId(slice),
     run: (slice: SliceDetails) => runQueryInNewTab(
-        `SELECT IMPORT('android.monitor_contention');
-         DROP TABLE IF EXISTS FAST;
-         CREATE TABLE FAST
-         AS
-         WITH slice_process AS (
-         SELECT process.name, process.upid FROM slice
-         JOIN thread_track ON thread_track.id = slice.track_id
-         JOIN thread USING(utid)
-         JOIN process USING(upid)
-         WHERE slice.id = ${slice.id}
-         )
-         SELECT *,
-         IIF(blocked_thread_name LIKE 'binder:%', 'binder', blocked_thread_name)
-          AS blocked_thread_name_norm,
-         IIF(blocking_thread_name LIKE 'binder:%', 'binder', blocking_thread_name)
-          AS blocking_thread_name_norm
-         FROM android_monitor_contention_chain, slice_process
-         WHERE android_monitor_contention_chain.upid = slice_process.upid;
+        `
+         SELECT IMPORT('android.monitor_contention');
 
-         WITH
-         R AS (
-         SELECT
-           id,
-           dur,
-           CAT_STACKS(blocked_thread_name_norm || ':' || short_blocked_method,
-             blocking_thread_name_norm || ':' || short_blocking_method) AS stack
-         FROM FAST
-         WHERE parent_id IS NULL
-         UNION ALL
-         SELECT
-         c.id,
-         c.dur AS dur,
-         CAT_STACKS(stack, blocking_thread_name_norm || ':' || short_blocking_method) AS stack
-         FROM FAST c, R AS p
-         WHERE p.id = c.parent_id
-         )
-         SELECT TITLE.process_name, EXPERIMENTAL_PROFILE(stack, 'duration', 'ns', dur) AS pprof
-         FROM R, (SELECT process_name FROM FAST LIMIT 1) TITLE;`,
+         SELECT * FROM android_monitor_contention_graph(${getUpidFromSlice(slice)});
+        `,
         'Lock graph',
         ),
   },
diff --git a/ui/src/frontend/index.ts b/ui/src/frontend/index.ts
index bd2e3c1..caa483e 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -25,6 +25,7 @@
 import {CommandManager} from '../common/commands';
 import {createEmptyState} from '../common/empty_state';
 import {RECORDING_V2_FLAG} from '../common/feature_flags';
+import {flattenArgs, traceEvent} from '../common/metatracing';
 import {pluginManager, pluginRegistry} from '../common/plugins';
 import {State} from '../common/state';
 import {initWasm} from '../common/wasm_engine_proxy';
@@ -86,9 +87,13 @@
 
   dispatchMultiple(actions: DeferredAction[]) {
     const edits = actions.map((action) => {
-      return (draft: Draft<State>) => {
-        (StateActions as any)[action.type](draft, action.args);
-      };
+      return traceEvent(`action.${action.type}`, () => {
+        return (draft: Draft<State>) => {
+          (StateActions as any)[action.type](draft, action.args);
+        };
+      }, {
+        args: flattenArgs(action.args),
+      });
     });
     globals.store.edit(edits);
   }
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 63c2bd8..09ed04f 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -201,10 +201,12 @@
     this.repositionCanvas();
 
     this.trash.add(new SimpleResizeObserver(dom, () => {
-      this.readParentSizeFromDom(dom);
-      this.updateCanvasDimensions();
-      this.repositionCanvas();
-      raf.scheduleFullRedraw();
+      const parentSizeChanged = this.readParentSizeFromDom(dom);
+      if (parentSizeChanged) {
+        this.updateCanvasDimensions();
+        this.repositionCanvas();
+        this.redrawCanvas();
+      }
     }));
 
     // TODO(dproy): Handle change in doesScroll attribute.
diff --git a/ui/src/frontend/recording/android_settings.ts b/ui/src/frontend/recording/android_settings.ts
index 4e41ac1..6568187 100644
--- a/ui/src/frontend/recording/android_settings.ts
+++ b/ui/src/frontend/recording/android_settings.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 
-import {DataSourceDescriptor} from '../../common/protos';
+import {DataSourceDescriptor} from '../../core/protos';
 import {globals} from '../globals';
 import {
   Dropdown,
diff --git a/ui/src/frontend/recording/memory_settings.ts b/ui/src/frontend/recording/memory_settings.ts
index 002d6db..1db2b75 100644
--- a/ui/src/frontend/recording/memory_settings.ts
+++ b/ui/src/frontend/recording/memory_settings.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 
-import {MeminfoCounters, VmstatCounters} from '../../common/protos';
+import {MeminfoCounters, VmstatCounters} from '../../core/protos';
 import {globals} from '../globals';
 import {
   Dropdown,
diff --git a/ui/src/frontend/rpc_http_dialog.ts b/ui/src/frontend/rpc_http_dialog.ts
index 7bd0a5f..024a113 100644
--- a/ui/src/frontend/rpc_http_dialog.ts
+++ b/ui/src/frontend/rpc_http_dialog.ts
@@ -17,7 +17,7 @@
 import {assertExists} from '../base/logging';
 import {Actions} from '../common/actions';
 import {HttpRpcEngine, RPC_URL} from '../common/http_rpc_engine';
-import {StatusResult} from '../common/protos';
+import {StatusResult} from '../core/protos';
 import {VERSION} from '../gen/perfetto_version';
 import {perfetto} from '../gen/protos';
 
diff --git a/ui/src/plugins/com.example.Skeleton/OWNERS b/ui/src/plugins/com.example.Skeleton/OWNERS
new file mode 100644
index 0000000..1acc35b
--- /dev/null
+++ b/ui/src/plugins/com.example.Skeleton/OWNERS
@@ -0,0 +1 @@
+# SKELETON: Add your email to this file!
diff --git a/ui/src/plugins/com.example.Skeleton/index.ts b/ui/src/plugins/com.example.Skeleton/index.ts
new file mode 100644
index 0000000..c7735c0
--- /dev/null
+++ b/ui/src/plugins/com.example.Skeleton/index.ts
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {
+  EngineProxy,
+  PluginContext,
+  Store,
+  TracePlugin,
+} from '../../public';
+
+interface State {}
+
+// SKELETON: Rename this class to match your plugin.
+class Skeleton implements TracePlugin {
+  static migrate(_initialState: unknown): State {
+    return {};
+  }
+
+  constructor(_store: Store<State>, _engine: EngineProxy) {}
+
+  dispose(): void {}
+}
+
+export const plugin = {
+  // SKELETON: Update pluginId to match the directory of the plugin.
+  pluginId: 'com.example.Skeleton',
+  activate: (ctx: PluginContext) => {
+    ctx.registerTracePluginFactory(Skeleton);
+  },
+};