Merge "[stdlib]: Add util methods to generate monitor contention graphs" into main
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/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/tools/check_imports.py b/python/tools/check_imports.py
index 9845d73..8313702 100755
--- a/python/tools/check_imports.py
+++ b/python/tools/check_imports.py
@@ -149,6 +149,16 @@
         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/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/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/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_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/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 6099de5..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';
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/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);
+  },
+};