Merge "ui: add --bigtrace" into main
diff --git a/Android.bp b/Android.bp
index 4e05485..bf201a2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -11499,6 +11499,7 @@
         "src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql",
         "src/trace_processor/metrics/sql/android/startup/mcycles_per_launch.sql",
         "src/trace_processor/metrics/sql/android/startup/slice_functions.sql",
+        "src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql",
         "src/trace_processor/metrics/sql/android/startup/system_state.sql",
         "src/trace_processor/metrics/sql/android/startup/thread_state_breakdown.sql",
         "src/trace_processor/metrics/sql/android/unsymbolized_frames.sql",
@@ -11758,26 +11759,14 @@
         "src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/screenshots.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/slices.sql",
+        "src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startup_events.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_maxsdk28.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk29.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk33.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/statsd.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/thread.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/histograms.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/speedometer.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql",
-        "src/trace_processor/perfetto_sql/stdlib/chrome/vsync_intervals.sql",
+        "src/trace_processor/perfetto_sql/stdlib/chrome/**/*.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/args.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/counters.sql",
         "src/trace_processor/perfetto_sql/stdlib/common/cpus.sql",
@@ -11795,6 +11784,7 @@
         "src/trace_processor/perfetto_sql/stdlib/intervals/overlap.sql",
         "src/trace_processor/perfetto_sql/stdlib/linux/cpu_idle.sql",
         "src/trace_processor/perfetto_sql/stdlib/pkvm/hypervisor.sql",
+        "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
         "src/trace_processor/perfetto_sql/stdlib/sched/thread_level_parallelism.sql",
     ],
     cmd: "$(location tools/gen_amalgamated_sql.py) --namespace=stdlib --cpp-out=$(out) $(in)",
diff --git a/BUILD b/BUILD
index 3500e82..affa401 100644
--- a/BUILD
+++ b/BUILD
@@ -1918,6 +1918,7 @@
         "src/trace_processor/metrics/sql/android/startup/launches_minsdk33.sql",
         "src/trace_processor/metrics/sql/android/startup/mcycles_per_launch.sql",
         "src/trace_processor/metrics/sql/android/startup/slice_functions.sql",
+        "src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql",
         "src/trace_processor/metrics/sql/android/startup/system_state.sql",
         "src/trace_processor/metrics/sql/android/startup/thread_state_breakdown.sql",
         "src/trace_processor/metrics/sql/android/unsymbolized_frames.sql",
@@ -2219,6 +2220,7 @@
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_android_startup_startup",
     srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startup_events.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_maxsdk28.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk29.sql",
         "src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk33.sql",
@@ -2304,6 +2306,14 @@
     ],
 )
 
+# GN target: //src/trace_processor/perfetto_sql/stdlib/prelude:prelude
+perfetto_filegroup(
+    name = "src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
+    srcs = [
+        "src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql",
+    ],
+)
+
 # GN target: //src/trace_processor/perfetto_sql/stdlib/sched:sched
 perfetto_filegroup(
     name = "src_trace_processor_perfetto_sql_stdlib_sched_sched",
@@ -2324,6 +2334,7 @@
         ":src_trace_processor_perfetto_sql_stdlib_intervals_intervals",
         ":src_trace_processor_perfetto_sql_stdlib_linux_linux",
         ":src_trace_processor_perfetto_sql_stdlib_pkvm_pkvm",
+        ":src_trace_processor_perfetto_sql_stdlib_prelude_prelude",
         ":src_trace_processor_perfetto_sql_stdlib_sched_sched",
     ],
     outs = [
diff --git a/BUILD.gn b/BUILD.gn
index 0369d78..63b418e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -315,7 +315,12 @@
       ]
     }
   }
-  component("libtrace_processor") {
+
+  # In Chromium, we want to ensure that we don't link dynamically against sqlite
+  # (as Chromium also uses a more restricted version of sqlite which is actually
+  # shipped to the users).
+  # source_set helps us to achieve that.
+  source_set("libtrace_processor") {
     public_configs = [ "gn:public_config" ]
     deps = [ "src/trace_processor:lib" ]
     configs -= [ "//build/config/compiler:chromium_code" ]
diff --git a/CHANGELOG b/CHANGELOG
index 6c75a5c..1c5e81b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,15 +1,30 @@
 Unreleased:
   Tracing service and probes:
+    *
+  Trace Processor:
+    *
+  UI:
+    *
+  SDK:
+    *
+
+
+v39.0 - 2023-11-15:
+  Tracing service and probes:
     * Added reporting of TZ offset under system_info.timezone_off_mins .
     * Added no_flush option to DataSourceDescriptor to avoid unnecessary IPC
       roundtrips to flush data sources like track_event that rely uniquely on
       server-side scraping.
+    * Added support for running on Linux & Android systems configured with 16K
+      pagetables.
   Trace Processor:
-    *
+    * New android_boot metric.
+    * Added new PerfettoSQL syntax (CREATE PERFETTO VIEW) for adding schemas to views.
+    * Support perf.data import format.
+    * Add dvfs and cpu_idle to stdlib.
   UI:
     * Add a new type of debug tracks: counter.
-  SDK:
-    *
+    * Improved visualization of timestamps for durations.
 
 
 v38.0 - 2023-10-10:
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index b5f567d..3825a02 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -125,7 +125,12 @@
   # If no GN files were modified, bail out.
   def build_file_filter(x):
     return input_api.FilterSourceFile(
-        x, files_to_check=('.*BUILD[.]gn$', '.*[.]gni$', tool))
+        x,
+        files_to_check=('.*BUILD[.]gn$', '.*[.]gni$', tool),
+        # Do not require Android.bp to be regenerated for chrome
+        # stdlib changes.
+        files_to_skip=(
+            'src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn'))
 
   if not input_api.AffectedSourceFiles(build_file_filter):
     return []
diff --git a/docs/analysis/perfetto-sql-syntax.md b/docs/analysis/perfetto-sql-syntax.md
index cb9dd1b..3170ec8 100644
--- a/docs/analysis/perfetto-sql-syntax.md
+++ b/docs/analysis/perfetto-sql-syntax.md
@@ -42,6 +42,17 @@
 FROM android_startups;
 ```
 
+For interactive development, the key can contain a wildcards:
+```sql
+-- Include all modules under android/.
+INCLUDE PERFETTO MODULE android.*;
+
+-- Or all stdlib modules:
+INCLUDE PERFETTO MODULE *;
+
+-- However, note, that both patterns are not allowed in stdlib.
+```
+
 ## Defining functions
 `CREATE PEFETTO FUNCTION` allows functions to be defined in SQL. The syntax is
 similar to the syntax in PostgreSQL or GoogleSQL.
diff --git a/docs/contributing/ui-plugins.md b/docs/contributing/ui-plugins.md
index 3b84547..939e334 100644
--- a/docs/contributing/ui-plugins.md
+++ b/docs/contributing/ui-plugins.md
@@ -115,6 +115,26 @@
 - [dev.perfetto.CoreCommands](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.CoreCommands/index.ts).
 - [dev.perfetto.ExampleState](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/plugins/dev.perfetto.ExampleState/index.ts).
 
+#### Hotkeys
+
+A default hotkey may be provided when registering a command.
+
+```typescript
+ctx.registerCommand({
+  id: 'dev.perfetto.ExampleSimpleCommand#LogHelloWorld',
+  name: 'Log "Hello, World!"',
+  callback: () => console.log('Hello, World!'),
+  defaultHotkey: 'Shift+H',
+});
+```
+
+Even though the hotkey is a string, it's format checked at compile time using 
+typescript's [template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html).
+
+See [hotkey.ts](https://cs.android.com/android/platform/superproject/main/+/main:external/perfetto/ui/src/base/hotkeys.ts)
+for more details on how the hotkey syntax works, and for the available keys and
+modifiers.
+
 ### Tracks
 TBD
 
diff --git a/docs/data-sources/battery-counters.md b/docs/data-sources/battery-counters.md
index 66df146..df1a701 100644
--- a/docs/data-sources/battery-counters.md
+++ b/docs/data-sources/battery-counters.md
@@ -130,7 +130,7 @@
 Android [IPowerStats HAL][power-hal].
 
 Googlers: See [go/power-rails-internal-doc](http://go/power-rails-internal-doc)
-for instructions on how to change the refault rail selection on Pixel devices.
+for instructions on how to change the default rail selection on Pixel devices.
 
 [power-hal]: https://cs.android.com/android/platform/superproject/+/main:hardware/interfaces/power/stats/1.0/IPowerStats.hal
 
diff --git a/docs/instrumentation/tracing-sdk.md b/docs/instrumentation/tracing-sdk.md
index 0cf0eb0..783c989 100644
--- a/docs/instrumentation/tracing-sdk.md
+++ b/docs/instrumentation/tracing-sdk.md
@@ -30,7 +30,7 @@
 To start using the Client API, first check out the latest SDK release:
 
 ```bash
-git clone https://android.googlesource.com/platform/external/perfetto -b v37.0
+git clone https://android.googlesource.com/platform/external/perfetto -b v39.0
 ```
 
 The SDK consists of two files, `sdk/perfetto.h` and `sdk/perfetto.cc`. These are
diff --git a/examples/sdk/README.md b/examples/sdk/README.md
index 2d2de1c..e491db0 100644
--- a/examples/sdk/README.md
+++ b/examples/sdk/README.md
@@ -15,7 +15,7 @@
 First, check out the latest Perfetto release:
 
 ```bash
-git clone https://android.googlesource.com/platform/external/perfetto -b v37.0
+git clone https://android.googlesource.com/platform/external/perfetto -b v39.0
 ```
 
 Then, build using CMake:
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index 715688d..8cd9b08 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -296,9 +296,12 @@
 
   # Enables httpd RPC support in the trace processor.
   # Further per-OS conditionals are applied in gn/BUILD.gn.
+  # Chromium+Win: httpd support depends on enable_perfetto_ipc, which is not
+  # enabled on Chromium+Win for now (see a comment there).
   enable_perfetto_trace_processor_httpd =
       enable_perfetto_trace_processor &&
-      (perfetto_build_standalone || perfetto_build_with_android)
+      (perfetto_build_standalone || perfetto_build_with_android ||
+       (build_with_chromium && !is_win))
 
   # Enables Zlib support. This is used to compress traces (by the tracing
   # service and by the "perfetto" cmdline client) and to decompress traces (by
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index 19c8029..1fb8196 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -172,7 +172,10 @@
   libs = []
 
   if ((is_android || is_linux) && !is_wasm) {
-    ldflags += [ "-Wl,--build-id" ]
+    ldflags += [
+      "-Wl,--build-id",
+      "-Wl,-z,max-page-size=16384",
+    ]
   }
 
   if (is_clang || !is_win) {  # Clang or GCC, but not MSVC.
@@ -319,9 +322,11 @@
     include_dirs = win_msvc_inc_dirs  # Defined in msvc.gni.
   }
 
+  if (is_win) {
+    cflags += [ "/Zi" ]
+  }
   if (is_debug) {
     if (is_win) {
-      cflags += [ "/Z7" ]
       if (is_clang) {
         # Required to see symbols in windbg when building with clang-cl.exe.
         cflags += [ "-gcodeview-ghash" ]
@@ -394,7 +399,7 @@
   }
 }
 
-config("debug_symbols") {
+config("debug_noopt") {
   cflags = []
   if (is_win) {
     cflags = [ "/Od" ]
diff --git a/gn/standalone/BUILDCONFIG.gn b/gn/standalone/BUILDCONFIG.gn
index 3a1119d..05ed548 100644
--- a/gn/standalone/BUILDCONFIG.gn
+++ b/gn/standalone/BUILDCONFIG.gn
@@ -63,7 +63,6 @@
       target_cpu != host_cpu || target_os != host_os || target_triplet != ""
 }
 default_configs = [
-  "//gn/standalone:debug_symbols",
   "//gn/standalone:default",
   "//gn/standalone:extra_warnings",
   "//gn/standalone:no_exceptions",
@@ -74,13 +73,14 @@
   "//gn/standalone:c++17",
 ]
 
-if (is_win) {
-  default_configs += [ "//gn/standalone:win32_lean_and_mean" ]
+if (is_debug) {
+  default_configs += [ "//gn/standalone:debug_noopt" ]
+} else {
+  default_configs += [ "//gn/standalone:release" ]
 }
 
-if (!is_debug) {
-  default_configs -= [ "//gn/standalone:debug_symbols" ]
-  default_configs += [ "//gn/standalone:release" ]
+if (is_win) {
+  default_configs += [ "//gn/standalone:win32_lean_and_mean" ]
 }
 
 set_defaults("source_set") {
diff --git a/gn/standalone/toolchain/BUILD.gn b/gn/standalone/toolchain/BUILD.gn
index e462bd5..71f2966 100644
--- a/gn/standalone/toolchain/BUILD.gn
+++ b/gn/standalone/toolchain/BUILD.gn
@@ -420,7 +420,7 @@
   tool("cc") {
     precompiled_header_type = "msvc"
     pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
-    command = "$cc_wrapper $cc /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
+    command = "$cc_wrapper $cc /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\" /guard:cf /ZH:SHA_256"
     depsformat = "msvc"
     outputs =
         [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
@@ -430,7 +430,7 @@
   tool("cxx") {
     precompiled_header_type = "msvc"
     pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
-    command = "$cc_wrapper $cxx /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
+    command = "$cc_wrapper $cxx /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\" /guard:cf /ZH:SHA_256"
     depsformat = "msvc"
     outputs =
         [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj" ]
@@ -490,8 +490,7 @@
     pdbname = "$exename.pdb"
     rspfile = "$exename.rsp"
 
-    command =
-        "$linker /nologo /OUT:$exename ${sys_lib_flags} /PDB:$pdbname @$rspfile"
+    command = "$linker /nologo /guard:cf /DYNAMICBASE /OUT:$exename ${sys_lib_flags} /DEBUG /PDB:$pdbname @$rspfile"
     default_output_extension = ".exe"
     default_output_dir = "{{root_out_dir}}"
     outputs = [ exename ]
diff --git a/include/perfetto/ext/base/utils.h b/include/perfetto/ext/base/utils.h
index 99a9802..f3bfa1c 100644
--- a/include/perfetto/ext/base/utils.h
+++ b/include/perfetto/ext/base/utils.h
@@ -50,17 +50,21 @@
 namespace perfetto {
 namespace base {
 
-// Do not add new usages of kPageSize, consider using GetSysPageSize() below.
-// TODO(primiano): over time the semantic of kPageSize became too ambiguous.
-// Strictly speaking, this constant is incorrect on some new devices where the
-// page size can be 16K (e.g., crbug.com/1116576). Unfortunately too much code
-// ended up depending on kPageSize for purposes that are not strictly related
-// with the kernel's mm subsystem.
-constexpr size_t kPageSize = 4096;
+namespace internal {
+extern std::atomic<uint32_t> g_cached_page_size;
+uint32_t GetSysPageSizeSlowpath();
+}  // namespace internal
 
 // Returns the system's page size. Use this when dealing with mmap, madvise and
 // similar mm-related syscalls.
-uint32_t GetSysPageSize();
+// This function might be called in hot paths. Avoid calling getpagesize() all
+// the times, in many implementations getpagesize() calls sysconf() which is
+// not cheap.
+inline uint32_t GetSysPageSize() {
+  const uint32_t page_size =
+      internal::g_cached_page_size.load(std::memory_order_relaxed);
+  return page_size != 0 ? page_size : internal::GetSysPageSizeSlowpath();
+}
 
 template <typename T, size_t TSize>
 constexpr size_t ArraySize(const T (&)[TSize]) {
@@ -85,10 +89,16 @@
 }
 
 // Round up |size| to a multiple of |alignment| (must be a power of two).
+inline constexpr size_t AlignUp(size_t size, size_t alignment) {
+  return (size + alignment - 1) & ~(alignment - 1);
+}
+
+// TODO(primiano): clean this up and move all existing usages to the constexpr
+// version above.
 template <size_t alignment>
 constexpr size_t AlignUp(size_t size) {
   static_assert((alignment & (alignment - 1)) == 0, "alignment must be a pow2");
-  return (size + alignment - 1) & ~(alignment - 1);
+  return AlignUp(size, alignment);
 }
 
 inline bool IsAgain(int err) {
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
index 9057396..119fff4 100644
--- a/infra/config/recipes.cfg
+++ b/infra/config/recipes.cfg
@@ -3,17 +3,17 @@
   "deps": {
     "depot_tools": {
       "branch": "refs/heads/main",
-      "revision": "6b98cdcbc133ec6902e84da64617560b33f9febc",
+      "revision": "0c5e8652fe1fefee1c291cbf05ca3a41b9f66890",
       "url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
     },
     "recipe_engine": {
       "branch": "refs/heads/main",
-      "revision": "086386d9ca13cbcedba146391ff25241a4ec2dfd",
+      "revision": "94e9b251b56473c68935e0b42aad3fcbe95e3f6c",
       "url": "https://chromium.googlesource.com/infra/luci/recipes-py"
     }
   },
   "project_id": "perfetto",
-  "recipes_path": "infra/luci",
   "py3_only": true,
+  "recipes_path": "infra/luci",
   "repo_name": "perfetto"
 }
diff --git a/infra/luci/README.recipes.md b/infra/luci/README.recipes.md
index 085f220..c818fe4 100644
--- a/infra/luci/README.recipes.md
+++ b/infra/luci/README.recipes.md
@@ -3,20 +3,19 @@
 ## Table of Contents
 
 **[Recipe Modules](#Recipe-Modules)**
-  * [macos_sdk](#recipe_modules-macos_sdk) (Python3 ✅) &mdash; The `macos_sdk` module provides safe functions to access a semi-hermetic XCode installation.
-  * [windows_sdk](#recipe_modules-windows_sdk) (Python3 ✅)
+  * [macos_sdk](#recipe_modules-macos_sdk) &mdash; The `macos_sdk` module provides safe functions to access a semi-hermetic XCode installation.
+  * [windows_sdk](#recipe_modules-windows_sdk)
 
 **[Recipes](#Recipes)**
-  * [macos_sdk:examples/full](#recipes-macos_sdk_examples_full) (Python3 ✅)
-  * [perfetto](#recipes-perfetto) (Python3 ✅) &mdash; Recipe for building Perfetto.
-  * [windows_sdk:examples/full](#recipes-windows_sdk_examples_full) (Python3 ✅)
+  * [macos_sdk:examples/full](#recipes-macos_sdk_examples_full)
+  * [perfetto](#recipes-perfetto) &mdash; Recipe for building Perfetto.
+  * [windows_sdk:examples/full](#recipes-windows_sdk_examples_full)
 ## Recipe Modules
 
 ### *recipe_modules* / [macos\_sdk](/infra/luci/recipe_modules/macos_sdk)
 
 [DEPS](/infra/luci/recipe_modules/macos_sdk/__init__.py#15): [recipe\_engine/cipd][recipe_engine/recipe_modules/cipd], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/json][recipe_engine/recipe_modules/json], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/step][recipe_engine/recipe_modules/step]
 
-PYTHON_VERSION_COMPATIBILITY: PY3
 
 The `macos_sdk` module provides safe functions to access a semi-hermetic
 XCode installation.
@@ -66,7 +65,6 @@
 
 [DEPS](/infra/luci/recipe_modules/windows_sdk/__init__.py#15): [recipe\_engine/cipd][recipe_engine/recipe_modules/cipd], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/json][recipe_engine/recipe_modules/json], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/step][recipe_engine/recipe_modules/step]
 
-PYTHON_VERSION_COMPATIBILITY: PY3
 
 #### **class [WindowsSDKApi](/infra/luci/recipe_modules/windows_sdk/api.py#20)([RecipeApi][recipe_engine/wkt/RecipeApi]):**
 
@@ -86,14 +84,12 @@
 
 [DEPS](/infra/luci/recipe_modules/macos_sdk/examples/full.py#15): [macos\_sdk](#recipe_modules-macos_sdk), [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/step][recipe_engine/recipe_modules/step]
 
-PYTHON_VERSION_COMPATIBILITY: PY3
 
 &mdash; **def [RunSteps](/infra/luci/recipe_modules/macos_sdk/examples/full.py#23)(api):**
 ### *recipes* / [perfetto](/infra/luci/recipes/perfetto.py)
 
 [DEPS](/infra/luci/recipes/perfetto.py#18): [depot\_tools/gsutil][depot_tools/recipe_modules/gsutil], [macos\_sdk](#recipe_modules-macos_sdk), [windows\_sdk](#recipe_modules-windows_sdk), [recipe\_engine/buildbucket][recipe_engine/recipe_modules/buildbucket], [recipe\_engine/cipd][recipe_engine/recipe_modules/cipd], [recipe\_engine/context][recipe_engine/recipe_modules/context], [recipe\_engine/file][recipe_engine/recipe_modules/file], [recipe\_engine/path][recipe_engine/recipe_modules/path], [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/raw\_io][recipe_engine/recipe_modules/raw_io], [recipe\_engine/step][recipe_engine/recipe_modules/step]
 
-PYTHON_VERSION_COMPATIBILITY: PY3
 
 Recipe for building Perfetto.
 
@@ -108,19 +104,18 @@
 
 [DEPS](/infra/luci/recipe_modules/windows_sdk/examples/full.py#15): [windows\_sdk](#recipe_modules-windows_sdk), [recipe\_engine/platform][recipe_engine/recipe_modules/platform], [recipe\_engine/properties][recipe_engine/recipe_modules/properties], [recipe\_engine/step][recipe_engine/recipe_modules/step]
 
-PYTHON_VERSION_COMPATIBILITY: PY3
 
 &mdash; **def [RunSteps](/infra/luci/recipe_modules/windows_sdk/examples/full.py#23)(api):**
 
-[depot_tools/recipe_modules/gsutil]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/6b98cdcbc133ec6902e84da64617560b33f9febc/recipes/README.recipes.md#recipe_modules-gsutil
-[recipe_engine/recipe_modules/buildbucket]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-buildbucket
-[recipe_engine/recipe_modules/cipd]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-cipd
-[recipe_engine/recipe_modules/context]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-context
-[recipe_engine/recipe_modules/file]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-file
-[recipe_engine/recipe_modules/json]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-json
-[recipe_engine/recipe_modules/path]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-path
-[recipe_engine/recipe_modules/platform]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-platform
-[recipe_engine/recipe_modules/properties]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-properties
-[recipe_engine/recipe_modules/raw_io]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-raw_io
-[recipe_engine/recipe_modules/step]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/README.recipes.md#recipe_modules-step
-[recipe_engine/wkt/RecipeApi]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/086386d9ca13cbcedba146391ff25241a4ec2dfd/recipe_engine/recipe_api.py#886
+[depot_tools/recipe_modules/gsutil]: https://chromium.googlesource.com/chromium/tools/depot_tools.git/+/0c5e8652fe1fefee1c291cbf05ca3a41b9f66890/recipes/README.recipes.md#recipe_modules-gsutil
+[recipe_engine/recipe_modules/buildbucket]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-buildbucket
+[recipe_engine/recipe_modules/cipd]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-cipd
+[recipe_engine/recipe_modules/context]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-context
+[recipe_engine/recipe_modules/file]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-file
+[recipe_engine/recipe_modules/json]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-json
+[recipe_engine/recipe_modules/path]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-path
+[recipe_engine/recipe_modules/platform]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-platform
+[recipe_engine/recipe_modules/properties]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-properties
+[recipe_engine/recipe_modules/raw_io]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-raw_io
+[recipe_engine/recipe_modules/step]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/README.recipes.md#recipe_modules-step
+[recipe_engine/wkt/RecipeApi]: https://chromium.googlesource.com/infra/luci/recipes-py.git/+/94e9b251b56473c68935e0b42aad3fcbe95e3f6c/recipe_engine/recipe_api.py#902
diff --git a/infra/luci/recipe_modules/macos_sdk/examples/full.expected/mac.json b/infra/luci/recipe_modules/macos_sdk/examples/full.expected/mac.json
index 7d0f197..94406ba 100644
--- a/infra/luci/recipe_modules/macos_sdk/examples/full.expected/mac.json
+++ b/infra/luci/recipe_modules/macos_sdk/examples/full.expected/mac.json
@@ -19,7 +19,7 @@
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
       "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
       "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:e9b\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:e9b\",@@@",
       "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/mac_toolchain/resolved-platform\"@@@",
       "@@@STEP_LOG_LINE@json.output@      }@@@",
       "@@@STEP_LOG_LINE@json.output@    ]@@@",
diff --git a/infra/luci/recipe_modules/windows_sdk/examples/full.expected/win.json b/infra/luci/recipe_modules/windows_sdk/examples/full.expected/win.json
index 0f68f34..f9646c9 100644
--- a/infra/luci/recipe_modules/windows_sdk/examples/full.expected/win.json
+++ b/infra/luci/recipe_modules/windows_sdk/examples/full.expected/win.json
@@ -19,7 +19,7 @@
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
       "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
       "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-uploaded:2019-09\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-uploaded:2019-09\",@@@",
       "@@@STEP_LOG_LINE@json.output@        \"package\": \"chrome_internal/third_party/sdk/windows\"@@@",
       "@@@STEP_LOG_LINE@json.output@      }@@@",
       "@@@STEP_LOG_LINE@json.output@    ]@@@",
@@ -42,16 +42,16 @@
       "@@@STEP_LOG_LINE@json.output@  \"env\": {@@@",
       "@@@STEP_LOG_LINE@json.output@    \"PATH\": [@@@",
       "@@@STEP_LOG_LINE@json.output@      [@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
-      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
-      "@@@STEP_LOG_LINE@json.output@        \"win_sdk\", @@@",
-      "@@@STEP_LOG_LINE@json.output@        \"bin\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"win_sdk\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"bin\",@@@",
       "@@@STEP_LOG_LINE@json.output@        \"x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@      ]@@@",
-      "@@@STEP_LOG_LINE@json.output@    ], @@@",
+      "@@@STEP_LOG_LINE@json.output@    ],@@@",
       "@@@STEP_LOG_LINE@json.output@    \"VSINSTALLDIR\": [@@@",
       "@@@STEP_LOG_LINE@json.output@      [@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\",@@@",
       "@@@STEP_LOG_LINE@json.output@        \"..\\\\\"@@@",
       "@@@STEP_LOG_LINE@json.output@      ]@@@",
       "@@@STEP_LOG_LINE@json.output@    ]@@@",
diff --git a/infra/luci/recipes.py b/infra/luci/recipes.py
index 642a90c..05ca941 100755
--- a/infra/luci/recipes.py
+++ b/infra/luci/recipes.py
@@ -12,7 +12,6 @@
 # evaluates to re-exec'ing this script in unbuffered mode.
 # pylint: disable=pointless-string-statement
 ''''exec python3 -u -- "$0" ${1+"$@"} # '''
-# vi: syntax=python
 """Bootstrap script to clone and forward to the recipe engine tool.
 
 *******************
@@ -29,16 +28,14 @@
 import json
 import logging
 import os
+import shutil
 import subprocess
 import sys
 
-from collections import namedtuple
-from io import open  # pylint: disable=redefined-builtin
+import urllib.parse as urlparse
 
-try:
-  import urllib.parse as urlparse
-except ImportError:
-  import urlparse
+from collections import namedtuple
+
 
 # The dependency entry for the recipe_engine in the client repo's recipes.cfg
 #
@@ -52,8 +49,8 @@
 class MalformedRecipesCfg(Exception):
 
   def __init__(self, msg, path):
-    full_message = 'malformed recipes.cfg: %s: %r' % (msg, path)
-    super(MalformedRecipesCfg, self).__init__(full_message)
+    full_message = f'malformed recipes.cfg: {msg}: {path!r}'
+    super().__init__(full_message)
 
 
 def parse(repo_root, recipes_cfg_path):
@@ -70,27 +67,23 @@
     recipes_path (str) - native path to where the recipes live inside of the
       current repo (i.e. the folder containing `recipes/` and/or
       `recipe_modules`)
-    py3_only (bool) - True if this repo has been marked as ONLY supporting
-      python3.
   """
-  with open(recipes_cfg_path, 'r') as fh:
-    pb = json.load(fh)
-  py3_only = pb.get('py3_only', False)
+  with open(recipes_cfg_path, 'r', encoding='utf-8') as file:
+    recipes_cfg = json.load(file)
 
   try:
-    if pb['api_version'] != 2:
-      raise MalformedRecipesCfg('unknown version %d' % pb['api_version'],
-                                recipes_cfg_path)
+    if (version := recipes_cfg['api_version']) != 2:
+      raise MalformedRecipesCfg(f'unknown version {version}', recipes_cfg_path)
 
     # If we're running ./recipes.py from the recipe_engine repo itself, then
     # return None to signal that there's no EngineDep.
-    repo_name = pb.get('repo_name')
+    repo_name = recipes_cfg.get('repo_name')
     if not repo_name:
-      repo_name = pb['project_id']
+      repo_name = recipes_cfg['project_id']
     if repo_name == 'recipe_engine':
-      return None, pb.get('recipes_path', ''), py3_only
+      return None, recipes_cfg.get('recipes_path', '')
 
-    engine = pb['deps']['recipe_engine']
+    engine = recipes_cfg['deps']['recipe_engine']
 
     if 'url' not in engine:
       raise MalformedRecipesCfg(
@@ -99,7 +92,7 @@
 
     engine.setdefault('revision', '')
     engine.setdefault('branch', 'refs/heads/main')
-    recipes_path = pb.get('recipes_path', '')
+    recipes_path = recipes_cfg.get('recipes_path', '')
 
     # TODO(iannucci): only support absolute refs
     if not engine['branch'].startswith('refs/'):
@@ -107,9 +100,9 @@
 
     recipes_path = os.path.join(repo_root,
                                 recipes_path.replace('/', os.path.sep))
-    return EngineDep(**engine), recipes_path, py3_only
+    return EngineDep(**engine), recipes_path
   except KeyError as ex:
-    raise MalformedRecipesCfg(str(ex), recipes_cfg_path)
+    raise MalformedRecipesCfg(str(ex), recipes_cfg_path) from ex
 
 
 IS_WIN = sys.platform.startswith(('win', 'cygwin'))
@@ -124,15 +117,6 @@
   return os.path.isfile(path) and os.access(path, os.X_OK)
 
 
-# TODO: Use shutil.which once we switch to Python3.
-def _is_on_path(basename):
-  for path in os.environ['PATH'].split(os.pathsep):
-    full_path = os.path.join(path, basename)
-    if _is_executable(full_path):
-      return True
-  return False
-
-
 def _subprocess_call(argv, **kwargs):
   logging.info('Running %r', argv)
   return subprocess.call(argv, **kwargs)
@@ -156,27 +140,27 @@
     * an override for the recipe engine in the form of `-O recipe_engine=/path`
     * the --package option.
   """
-  PREFIX = 'recipe_engine='
+  override_prefix = 'recipe_engine='
 
-  p = argparse.ArgumentParser(add_help=False)
-  p.add_argument('-O', '--project-override', action='append')
-  p.add_argument('--package', type=os.path.abspath)
-  args, _ = p.parse_known_args(argv)
+  parser = argparse.ArgumentParser(add_help=False)
+  parser.add_argument('-O', '--project-override', action='append')
+  parser.add_argument('--package', type=os.path.abspath)
+  args, _ = parser.parse_known_args(argv)
   for override in args.project_override or ():
-    if override.startswith(PREFIX):
-      return override[len(PREFIX):], args.package
+    if override.startswith(override_prefix):
+      return override[len(override_prefix):], args.package
   return None, args.package
 
 
 def checkout_engine(engine_path, repo_root, recipes_cfg_path):
   """Checks out the recipe_engine repo pinned in recipes.cfg.
 
-  Returns the path to the recipe engine repo and the py3_only boolean.
+  Returns the path to the recipe engine repo.
   """
-  dep, recipes_path, py3_only = parse(repo_root, recipes_cfg_path)
+  dep, recipes_path = parse(repo_root, recipes_cfg_path)
   if dep is None:
     # we're running from the engine repo already!
-    return os.path.join(repo_root, recipes_path), py3_only
+    return os.path.join(repo_root, recipes_path)
 
   url = dep.url
 
@@ -190,20 +174,18 @@
     # Ensure that we have the recipe engine cloned.
     engine_path = os.path.join(recipes_path, '.recipe_deps', 'recipe_engine')
 
-    with open(os.devnull, 'w') as NUL:
-      # Note: this logic mirrors the logic in recipe_engine/fetch.py
-      _git_check_call(['init', engine_path], stdout=NUL)
+    # Note: this logic mirrors the logic in recipe_engine/fetch.py
+    _git_check_call(['init', engine_path], stdout=subprocess.DEVNULL)
 
-      try:
-        _git_check_call(['rev-parse', '--verify',
-                         '%s^{commit}' % revision],
-                        cwd=engine_path,
-                        stdout=NUL,
-                        stderr=NUL)
-      except subprocess.CalledProcessError:
-        _git_check_call(['fetch', '--quiet', url, branch],
-                        cwd=engine_path,
-                        stdout=NUL)
+    try:
+      _git_check_call(['rev-parse', '--verify', f'{revision}^{{commit}}'],
+                      cwd=engine_path,
+                      stdout=subprocess.DEVNULL,
+                      stderr=subprocess.DEVNULL)
+    except subprocess.CalledProcessError:
+      _git_check_call(['fetch', '--quiet', url, branch],
+                      cwd=engine_path,
+                      stdout=subprocess.DEVNULL)
 
     try:
       _git_check_call(['diff', '--quiet', revision], cwd=engine_path)
@@ -213,21 +195,21 @@
         os.remove(index_lock)
       except OSError as exc:
         if exc.errno != errno.ENOENT:
-          logging.warn('failed to remove %r, reset will fail: %s', index_lock,
-                       exc)
+          logging.warning('failed to remove %r, reset will fail: %s',
+                          index_lock, exc)
       _git_check_call(['reset', '-q', '--hard', revision], cwd=engine_path)
 
     # If the engine has refactored/moved modules we need to clean all .pyc files
     # or things will get squirrely.
     _git_check_call(['clean', '-qxf'], cwd=engine_path)
 
-  return engine_path, py3_only
+  return engine_path
 
 
 def main():
   for required_binary in REQUIRED_BINARIES:
-    if not _is_on_path(required_binary):
-      return 'Required binary is not found on PATH: %s' % required_binary
+    if not shutil.which(required_binary):
+      return f'Required binary is not found on PATH: {required_binary}'
 
   if '--verbose' in sys.argv:
     logging.getLogger().setLevel(logging.INFO)
@@ -247,16 +229,31 @@
     repo_root = os.path.abspath(repo_root).decode()
     recipes_cfg_path = os.path.join(repo_root, 'infra', 'config', 'recipes.cfg')
     args = ['--package', recipes_cfg_path] + args
-  engine_path, py3_only = checkout_engine(engine_override, repo_root,
-                                          recipes_cfg_path)
+  engine_path = checkout_engine(engine_override, repo_root, recipes_cfg_path)
 
-  using_py3 = py3_only or os.getenv('RECIPES_USE_PY3') == 'true'
-  vpython = ('vpython' + ('3' if using_py3 else '') + _BAT)
-  if not _is_on_path(vpython):
-    return 'Required binary is not found on PATH: %s' % vpython
+  vpython = 'vpython3' + _BAT
+  if not shutil.which(vpython):
+    return f'Required binary is not found on PATH: {vpython}'
+
+  # We unset PYTHONPATH here in case the user has conflicting environmental
+  # things we don't want them to leak through into the recipe_engine which
+  # manages its environment entirely via vpython.
+  #
+  # NOTE: os.unsetenv unhelpfully doesn't exist on all platforms until python3.9
+  # so we have to use the cutesy `pop` formulation below until then...
+  os.environ.pop("PYTHONPATH", None)
+
+  spec = '.vpython3'
+  debugger = os.environ.get('RECIPE_DEBUGGER', '')
+  if debugger.startswith('pycharm'):
+    spec = '.pycharm.vpython3'
+  elif debugger.startswith('vscode'):
+    spec = '.vscode.vpython3'
 
   argv = ([
       vpython,
+      '-vpython-spec',
+      os.path.join(engine_path, spec),
       '-u',
       os.path.join(engine_path, 'recipe_engine', 'main.py'),
   ] + args)
@@ -264,13 +261,14 @@
   if IS_WIN:
     # No real 'exec' on windows; set these signals to ignore so that they
     # propagate to our children but we still wait for the child process to quit.
-    import signal
-    signal.signal(signal.SIGBREAK, signal.SIG_IGN)
+    import signal  # pylint: disable=import-outside-toplevel
+    signal.signal(signal.SIGBREAK, signal.SIG_IGN)  # pylint: disable=no-member
     signal.signal(signal.SIGINT, signal.SIG_IGN)
     signal.signal(signal.SIGTERM, signal.SIG_IGN)
     return _subprocess_call(argv)
-  else:
-    os.execvp(argv[0], argv)
+
+  os.execvp(argv[0], argv)
+  return -1  # should never occur
 
 
 if __name__ == '__main__':
diff --git a/infra/luci/recipes/perfetto.expected/ci_android.json b/infra/luci/recipes/perfetto.expected/ci_android.json
index eb09d35..d4d4207 100644
--- a/infra/luci/recipes/perfetto.expected/ci_android.json
+++ b/infra/luci/recipes/perfetto.expected/ci_android.json
@@ -312,7 +312,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -349,7 +349,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -420,7 +420,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -457,7 +457,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -528,7 +528,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -565,7 +565,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -636,7 +636,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -673,7 +673,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -744,7 +744,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -781,7 +781,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -852,7 +852,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -889,7 +889,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/android-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1053,7 +1053,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1090,7 +1090,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1161,7 +1161,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1198,7 +1198,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1269,7 +1269,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1306,7 +1306,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1377,7 +1377,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1414,7 +1414,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1485,7 +1485,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1522,7 +1522,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1593,7 +1593,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1630,7 +1630,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/android-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1794,7 +1794,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1831,7 +1831,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1902,7 +1902,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1939,7 +1939,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2010,7 +2010,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2047,7 +2047,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2118,7 +2118,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2155,7 +2155,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2226,7 +2226,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2263,7 +2263,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2334,7 +2334,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2371,7 +2371,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/android-x86\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2535,7 +2535,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2572,7 +2572,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2643,7 +2643,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2680,7 +2680,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2751,7 +2751,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2788,7 +2788,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2859,7 +2859,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2896,7 +2896,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2967,7 +2967,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -3004,7 +3004,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -3075,7 +3075,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -3112,7 +3112,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/android-x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
diff --git a/infra/luci/recipes/perfetto.expected/ci_linux.json b/infra/luci/recipes/perfetto.expected/ci_linux.json
index 43d3d4a..508ea21 100644
--- a/infra/luci/recipes/perfetto.expected/ci_linux.json
+++ b/infra/luci/recipes/perfetto.expected/ci_linux.json
@@ -312,7 +312,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -349,7 +349,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -420,7 +420,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -457,7 +457,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -528,7 +528,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -565,7 +565,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -636,7 +636,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -673,7 +673,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -744,7 +744,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -781,7 +781,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -852,7 +852,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -889,7 +889,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1053,7 +1053,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1090,7 +1090,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1161,7 +1161,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1198,7 +1198,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1269,7 +1269,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1306,7 +1306,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1377,7 +1377,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1414,7 +1414,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1485,7 +1485,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1522,7 +1522,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1593,7 +1593,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1630,7 +1630,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1794,7 +1794,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1831,7 +1831,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1902,7 +1902,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1939,7 +1939,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2010,7 +2010,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2047,7 +2047,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2118,7 +2118,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2155,7 +2155,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2226,7 +2226,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2263,7 +2263,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2334,7 +2334,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2371,7 +2371,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
diff --git a/infra/luci/recipes/perfetto.expected/ci_mac.json b/infra/luci/recipes/perfetto.expected/ci_mac.json
index 60543d4..b0a478a 100644
--- a/infra/luci/recipes/perfetto.expected/ci_mac.json
+++ b/infra/luci/recipes/perfetto.expected/ci_mac.json
@@ -193,7 +193,7 @@
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
       "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
       "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:e9b\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:e9b\",@@@",
       "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/mac_toolchain/resolved-platform\"@@@",
       "@@@STEP_LOG_LINE@json.output@      }@@@",
       "@@@STEP_LOG_LINE@json.output@    ]@@@",
@@ -435,7 +435,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -472,7 +472,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -543,7 +543,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -580,7 +580,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -651,7 +651,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -688,7 +688,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -759,7 +759,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -796,7 +796,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -867,7 +867,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -904,7 +904,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -975,7 +975,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1012,7 +1012,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/mac-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1058,7 +1058,7 @@
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
       "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
       "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:e9b\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-git_revision:e9b\",@@@",
       "@@@STEP_LOG_LINE@json.output@        \"package\": \"infra/tools/mac_toolchain/resolved-platform\"@@@",
       "@@@STEP_LOG_LINE@json.output@      }@@@",
       "@@@STEP_LOG_LINE@json.output@    ]@@@",
@@ -1300,7 +1300,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1337,7 +1337,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1408,7 +1408,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1445,7 +1445,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1516,7 +1516,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1553,7 +1553,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1624,7 +1624,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1661,7 +1661,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1732,7 +1732,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1769,7 +1769,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1840,7 +1840,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1877,7 +1877,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/mac-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
diff --git a/infra/luci/recipes/perfetto.expected/ci_tag.json b/infra/luci/recipes/perfetto.expected/ci_tag.json
index d3227ee..b5dc047 100644
--- a/infra/luci/recipes/perfetto.expected/ci_tag.json
+++ b/infra/luci/recipes/perfetto.expected/ci_tag.json
@@ -312,7 +312,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -351,7 +351,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -422,7 +422,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -461,7 +461,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -532,7 +532,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -571,7 +571,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -642,7 +642,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -681,7 +681,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -752,7 +752,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -791,7 +791,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -862,7 +862,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -901,7 +901,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1065,7 +1065,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1104,7 +1104,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1175,7 +1175,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1214,7 +1214,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1285,7 +1285,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1324,7 +1324,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1395,7 +1395,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1434,7 +1434,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1505,7 +1505,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1544,7 +1544,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1615,7 +1615,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1654,7 +1654,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-arm\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1818,7 +1818,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1857,7 +1857,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1928,7 +1928,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -1967,7 +1967,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2038,7 +2038,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2077,7 +2077,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/tracebox/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2148,7 +2148,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2187,7 +2187,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2258,7 +2258,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2297,7 +2297,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2368,7 +2368,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -2407,7 +2407,7 @@
       "@@@STEP_NEST_LEVEL@2@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced_probes/linux-arm64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
diff --git a/infra/luci/recipes/perfetto.expected/ci_win.json b/infra/luci/recipes/perfetto.expected/ci_win.json
index 9687045..34f2d95 100644
--- a/infra/luci/recipes/perfetto.expected/ci_win.json
+++ b/infra/luci/recipes/perfetto.expected/ci_win.json
@@ -188,7 +188,7 @@
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
       "@@@STEP_LOG_LINE@json.output@    \"\": [@@@",
       "@@@STEP_LOG_LINE@json.output@      {@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-uploaded:2019-09\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"instance_id\": \"resolved-instance_id-of-uploaded:2019-09\",@@@",
       "@@@STEP_LOG_LINE@json.output@        \"package\": \"chrome_internal/third_party/sdk/windows\"@@@",
       "@@@STEP_LOG_LINE@json.output@      }@@@",
       "@@@STEP_LOG_LINE@json.output@    ]@@@",
@@ -224,16 +224,16 @@
       "@@@STEP_LOG_LINE@json.output@  \"env\": {@@@",
       "@@@STEP_LOG_LINE@json.output@    \"PATH\": [@@@",
       "@@@STEP_LOG_LINE@json.output@      [@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
-      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
-      "@@@STEP_LOG_LINE@json.output@        \"win_sdk\", @@@",
-      "@@@STEP_LOG_LINE@json.output@        \"bin\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"win_sdk\",@@@",
+      "@@@STEP_LOG_LINE@json.output@        \"bin\",@@@",
       "@@@STEP_LOG_LINE@json.output@        \"x64\"@@@",
       "@@@STEP_LOG_LINE@json.output@      ]@@@",
-      "@@@STEP_LOG_LINE@json.output@    ], @@@",
+      "@@@STEP_LOG_LINE@json.output@    ],@@@",
       "@@@STEP_LOG_LINE@json.output@    \"VSINSTALLDIR\": [@@@",
       "@@@STEP_LOG_LINE@json.output@      [@@@",
-      "@@@STEP_LOG_LINE@json.output@        \"..\", @@@",
+      "@@@STEP_LOG_LINE@json.output@        \"..\",@@@",
       "@@@STEP_LOG_LINE@json.output@        \"..\\\\\"@@@",
       "@@@STEP_LOG_LINE@json.output@      ]@@@",
       "@@@STEP_LOG_LINE@json.output@    ]@@@",
@@ -404,7 +404,7 @@
       "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/windows-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -441,7 +441,7 @@
       "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/trace_processor_shell/windows-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -512,7 +512,7 @@
       "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/windows-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -549,7 +549,7 @@
       "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traceconv/windows-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -620,7 +620,7 @@
       "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/windows-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -657,7 +657,7 @@
       "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/perfetto/windows-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -728,7 +728,7 @@
       "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/windows-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
@@ -765,7 +765,7 @@
       "@@@STEP_NEST_LEVEL@1@@@",
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"result\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"instance_id\": \"40-chars-fake-of-the-package-instance_id\",@@@",
       "@@@STEP_LOG_LINE@json.output@    \"package\": \"perfetto/traced/windows-amd64\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }@@@",
       "@@@STEP_LOG_LINE@json.output@}@@@",
diff --git a/infra/perfetto.dev/src/gen_stdlib_docs_md.py b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
index 3a61b37..b0ed5c7 100644
--- a/infra/perfetto.dev/src/gen_stdlib_docs_md.py
+++ b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
@@ -47,6 +47,24 @@
     long_s = []
     long_s.append(f'## Module: {self.module_name}')
 
+    if self.module_name == 'prelude':
+      # Prelude is a special module which is automatically imported and doesn't
+      # have any include keys.
+      objs = '\n'.join(obj for file in self.files_md for obj in file.objs)
+      if objs:
+        long_s.append('#### Views/Tables')
+        long_s.append(objs)
+      funs = '\n'.join(fun for file in self.files_md for fun in file.funs)
+      if funs:
+        long_s.append('#### Functions')
+        long_s.append(funs)
+      table_funs = '\n'.join(
+          view_fun for file in self.files_md for view_fun in file.view_funs)
+      if table_funs:
+        long_s.append('#### Table Functions')
+        long_s.append(table_funs)
+      return '\n'.join(long_s)
+
     for file in self.files_md:
       if not file.objs and not file.funs and not file.view_funs:
         continue
@@ -70,6 +88,7 @@
 
   def __init__(self, module_name, file_dict):
     self.import_key = file_dict['import_key']
+    import_key_name = self.import_key if module_name != 'prelude' else 'N/A'
     self.objs, self.funs, self.view_funs = [], [], []
     summary_objs_list, summary_funs_list, summary_view_funs_list = [], [], []
 
@@ -81,7 +100,7 @@
       # Add summary of imported view/table
       desc = data['desc'].split('.')[0]
       summary_objs_list.append(f'''[{data['name']}](#{anchor})|'''
-                               f'''{file_dict['import_key']}|'''
+                               f'''{import_key_name}|'''
                                f'''{desc}''')
 
       self.objs.append(f'''\n\n<a name="{anchor}"></a>'''
@@ -102,7 +121,7 @@
 
       # Add summary of imported function
       summary_funs_list.append(f'''[{data['name']}](#{anchor})|'''
-                               f'''{file_dict['import_key']}|'''
+                               f'''{import_key_name}|'''
                                f'''{data['return_type']}|'''
                                f'''{data['desc'].split('.')[0]}''')
       self.funs.append(
@@ -125,7 +144,7 @@
       anchor = rf'''view_fun/{module_name}/{data['name']}'''
       # Add summary of imported view function
       summary_view_funs_list.append(f'''[{data['name']}](#{anchor})|'''
-                                    f'''{file_dict['import_key']}|'''
+                                    f'''{import_key_name}|'''
                                     f'''{data['desc'].split('.')[0]}''')
 
       self.view_funs.append(f'''\n\n<a name="{anchor}"></a>'''
@@ -165,6 +184,7 @@
     modules_dict[module_name] = ModuleMd(module_name, module_files)
 
   common_module = modules_dict.pop('common')
+  prelude_module = modules_dict.pop('prelude')
 
   with open(args.output, 'w') as f:
     f.write('''
@@ -197,6 +217,9 @@
 FROM android_startups;
 ```
 
+Prelude is a special module is automatically imported. It contains key helper
+tables, views and functions which are universally useful.
+
 More information on importing modules is available in the
 [syntax documentation](/docs/analysis/perfetto-sql-syntax#including-perfettosql-modules)
 for the `INCLUDE PERFETTO MODULE` statement.
@@ -206,23 +229,29 @@
 ## Summary
 ''')
 
-    summary_objs = [common_module.summary_objs
-                   ] if common_module.summary_objs else []
+    summary_objs = [prelude_module.summary_objs
+                   ] if prelude_module.summary_objs else []
+    summary_objs += [common_module.summary_objs
+                    ] if common_module.summary_objs else []
     summary_objs += [
         module.summary_objs
         for name, module in modules_dict.items()
         if (module.summary_objs and name != 'experimental')
     ]
 
-    summary_funs = [common_module.summary_funs
-                   ] if common_module.summary_funs else []
+    summary_funs = [prelude_module.summary_funs
+                   ] if prelude_module.summary_funs else []
+    summary_funs += [common_module.summary_funs
+                    ] if common_module.summary_funs else []
     summary_funs += [
         module.summary_funs
         for name, module in modules_dict.items()
         if (module.summary_funs and name != 'experimental')
     ]
-    summary_view_funs = [common_module.summary_view_funs
-                        ] if common_module.summary_view_funs else []
+    summary_view_funs = [prelude_module.summary_view_funs
+                        ] if prelude_module.summary_view_funs else []
+    summary_view_funs += [common_module.summary_view_funs
+                         ] if common_module.summary_view_funs else []
     summary_view_funs += [
         module.summary_view_funs
         for name, module in modules_dict.items()
@@ -251,6 +280,8 @@
       f.write('\n')
 
     f.write('\n\n')
+    f.write(prelude_module.print_description())
+    f.write('\n')
     f.write(common_module.print_description())
     f.write('\n')
     f.write('\n'.join(
diff --git a/protos/perfetto/metrics/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto
index 599a07e..c5ad849 100644
--- a/protos/perfetto/metrics/android/startup_metric.proto
+++ b/protos/perfetto/metrics/android/startup_metric.proto
@@ -228,7 +228,13 @@
     optional int64 dex2oat_dur_ns = 7;
   }
 
-  // Next id: 21
+  // Contains detailed information for slow startup causes.
+  message SlowStartReasonDetailed {
+    optional string reason = 1;
+    optional string details = 2;
+  }
+
+  // Next id: 22
   message Startup {
     // Random id uniquely identifying an app startup in this trace.
     optional uint32 startup_id = 1;
@@ -299,6 +305,9 @@
     // Optional.
     repeated string slow_start_reason = 17;
 
+    // Same as slow_start_reason, but with more detailed information.
+    repeated SlowStartReasonDetailed slow_start_reason_detailed = 21;
+
     reserved 10;
   }
 
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index c518c7f..4021174 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -2052,7 +2052,13 @@
     optional int64 dex2oat_dur_ns = 7;
   }
 
-  // Next id: 21
+  // Contains detailed information for slow startup causes.
+  message SlowStartReasonDetailed {
+    optional string reason = 1;
+    optional string details = 2;
+  }
+
+  // Next id: 22
   message Startup {
     // Random id uniquely identifying an app startup in this trace.
     optional uint32 startup_id = 1;
@@ -2123,6 +2129,9 @@
     // Optional.
     repeated string slow_start_reason = 17;
 
+    // Same as slow_start_reason, but with more detailed information.
+    repeated SlowStartReasonDetailed slow_start_reason_detailed = 21;
+
     reserved 10;
   }
 
diff --git a/protos/perfetto/trace_processor/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index 8a6f511..48959ad 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -46,7 +46,8 @@
   // 7. Introduce GUESS_CPU_SIZE
   // 8. Add 'json' option to ComputeMetricArgs
   // 9. Add get_thread_state_summary_for_interval.
-  TRACE_PROCESSOR_CURRENT_API_VERSION = 9;
+  // 10. Add 'slice_is_ancestor' to stdlib.
+  TRACE_PROCESSOR_CURRENT_API_VERSION = 10;
 }
 
 // At lowest level, the wire-format of the RPC procol is a linear sequence of
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 9237f78..90cde7d 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -1377,6 +1377,10 @@
     STEP_SEND_BEGIN_MAIN_FRAME = 8;
     STEP_SUBMIT_COMPOSITOR_FRAME = 9;
     STEP_SURFACE_AGGREGATION = 10;
+    STEP_SEND_BUFFER_SWAP = 11;
+    STEP_BUFFER_SWAP_POST_SUBMIT = 12;
+    STEP_FINISH_BUFFER_SWAP = 13;
+    STEP_SWAP_BUFFERS_ACK = 14;
   }
   enum FrameSkippedReason {
     SKIPPED_REASON_UNKNOWN = 0;
@@ -1393,9 +1397,110 @@
   optional FrameSkippedReason frame_skipped_reason = 6;
 };
 
+message LibunwindstackUnwinder {
+  // The enum is a copy of ErrorCode enum inside third_party/libunwindstack,
+  // ideally this should be in sync with that.
+  enum ErrorCode {
+    ERROR_NONE = 0;            // No error.
+    ERROR_MEMORY_INVALID = 1;  // Memory read failed.
+    ERROR_UNWIND_INFO = 2;     // Unable to use unwind information to unwind.
+    ERROR_UNSUPPORTED = 3;     // Encountered unsupported feature.
+    ERROR_INVALID_MAP = 4;     // Unwind in an invalid map.
+    ERROR_MAX_FRAMES_EXCEEDED = 5;  // The number of frames exceed the total
+                                    // allowed.
+    ERROR_REPEATED_FRAME = 6;  // The last frame has the same pc/sp as the next.
+    ERROR_INVALID_ELF = 7;     // Unwind in an invalid elf.
+    ERROR_THREAD_DOES_NOT_EXIST = 8;  // Attempt to unwind a local thread that
+                                      // does not exist.
+    ERROR_THREAD_TIMEOUT = 9;  // Timeout trying to unwind a local thread.
+    ERROR_SYSTEM_CALL = 10;    // System call failed while unwinding.
+    ERROR_BAD_ARCH = 11;       // Arch invalid (none, or mismatched).
+    ERROR_MAPS_PARSE = 12;     // Failed to parse maps data.
+    ERROR_INVALID_PARAMETER_LIBUNWINDSTACK =
+        13;                  // Invalid parameter passed to function.
+    ERROR_PTRACE_CALL = 14;  // Ptrace call failed while unwinding.
+  }
+  optional ErrorCode error_code = 1;
+  optional int32 num_frames = 2;
+};
+
+message ScrollPredictorMetrics {
+  message EventFrameValue {
+    optional int64 event_trace_id = 1;
+    // The fractional pixels (can be fractional after the predictor adjusts in
+    // resampling of input) that the page was scrolled by this frame.
+    optional float delta_value_pixels = 2;
+  };
+  // Data from the previous, current, and next frame used to determine the
+  // values below as according to the metric doc:
+  // http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY.
+  optional EventFrameValue prev_event_frame_value = 1;
+  optional EventFrameValue cur_event_frame_value = 2;
+  optional EventFrameValue next_event_frame_value = 3;
+  // This is the amount of delta processed in this frame that was above the
+  // janky threshold (as defined by
+  // http://doc/1Y0u0Tq5eUZff75nYUzQVw6JxmbZAW9m64pJidmnGWsY)
+  optional float janky_value_pixels = 4;
+  // True if we are also missing frames (so multiple frames are being presented
+  // at once).
+  optional bool has_missed_vsyncs = 5;
+  // True if we're moving less than the slow scroll threshold as defined by the
+  // doc above.
+  optional bool is_slow_scroll = 6;
+};
+
+//
+// Critical User Interaction metrics
+//
+
+message PageLoad {
+  optional int64 navigation_id = 1;
+  optional string url = 2;
+}
+
+message StartUp {
+  // This enum must be kept up to date with LaunchCauseMetrics.LaunchCause.
+  enum LauchCauseType {
+    OTHER = 0;
+    CUSTOM_TAB = 1;
+    TWA = 2;
+    RECENTS = 3;
+    RECENTS_OR_BACK = 4;
+    FOREGROUND_WHEN_LOCKED = 5;
+    MAIN_LAUNCHER_ICON = 6;
+    MAIN_LAUNCHER_ICON_SHORTCUT = 7;
+    HOME_SCREEN_WIDGET = 8;
+    OPEN_IN_BROWSER_FROM_MENU = 9;
+    EXTERNAL_SEARCH_ACTION_INTENT = 10;
+    NOTIFICATION = 11;
+    EXTERNAL_VIEW_INTENT = 12;
+    OTHER_CHROME = 13;
+    WEBAPK_CHROME_DISTRIBUTOR = 14;
+    WEBAPK_OTHER_DISTRIBUTOR = 15;
+    HOME_SCREEN_SHORTCUT = 16;
+    SHARE_INTENT = 17;
+    NFC = 18;
+  }
+
+  optional int64 activity_id = 1;
+  optional LauchCauseType launch_cause = 2;
+}
+
+message WebContentInteraction {
+  enum Type {
+    INTERACTION_UNSPECIFIED = 0;
+    INTERACTION_KEYBOARD = 1;
+    INTERACTION_CLICK_TAP = 2;
+    INTERACTION_DRAG = 3;
+  }
+
+  optional Type type = 1;
+  optional int64 total_duration_ms = 2;
+}
+
 message ChromeTrackEvent {
   // Extension range for Chrome: 1000-1999
-  // Next ID: 1053
+  // Next ID: 1059
   extend TrackEvent {
     optional ChromeAppState chrome_app_state = 1000;
 
@@ -1509,5 +1614,15 @@
     optional ChromeGraphicsPipeline chrome_graphics_pipeline = 1052;
 
     optional CrasUnified chromeos_cras_unified = 1053;
+
+    optional LibunwindstackUnwinder libunwindstack_unwinder = 1054;
+
+    optional ScrollPredictorMetrics scroll_predictor_metrics = 1055;
+
+    optional PageLoad page_load = 1056;
+
+    optional StartUp startup = 1057;
+
+    optional WebContentInteraction web_content_interaction = 1058;
   }
 }
diff --git a/python/generators/sql_processing/docs_parse.py b/python/generators/sql_processing/docs_parse.py
index 1c3142d..981316d 100644
--- a/python/generators/sql_processing/docs_parse.py
+++ b/python/generators/sql_processing/docs_parse.py
@@ -68,10 +68,11 @@
     if upper:
       module_pattern = module_pattern.upper()
     starts_with_module_name = re.match(module_pattern, self.name, re.IGNORECASE)
-    if self.module == "common":
+    if self.module == "common" or self.module == "prelude":
       if starts_with_module_name:
-        self._error('Names of tables/views/functions in the "common" module '
-                    f'should not start with {module_pattern}')
+        self._error(
+            'Names of tables/views/functions in the "{self.module}" module '
+            f'should not start with {module_pattern}')
       return self.name
     if not starts_with_module_name:
       self._error('Names of tables/views/functions should be prefixed with the '
diff --git a/python/generators/sql_processing/utils.py b/python/generators/sql_processing/utils.py
index e9247fe..0a05a71 100644
--- a/python/generators/sql_processing/utils.py
+++ b/python/generators/sql_processing/utils.py
@@ -14,6 +14,7 @@
 
 from enum import Enum
 import re
+import os
 from typing import Dict, List
 
 NAME = r'[a-zA-Z_\d\{\}]+'
@@ -51,6 +52,9 @@
 DROP_TABLE_VIEW_PATTERN = update_pattern(fr'^DROP (TABLE|VIEW) IF EXISTS '
                                          fr'({NAME});$')
 
+INCLUDE_ALL_PATTERN = update_pattern(
+    fr'^INCLUDE PERFETTO MODULE [a-zA-Z0-9_\.]*\*;')
+
 CREATE_FUNCTION_PATTERN = update_pattern(
     # Function name.
     fr"CREATE (OR REPLACE)? PERFETTO FUNCTION ({NAME}) "
@@ -163,42 +167,28 @@
       errors.append('SELECT IMPORT is deprecated in trace processor. '
                     'Use INCLUDE PERFETTO MODULE instead.\n'
                     f'Offending file: {path}')
+
   return errors
 
 
 # Given SQL string check whether there is (not allowlisted) usage of
 # CREATE TABLE {name} AS.
-def check_banned_create_table_as(sql: str, filename: str,
+def check_banned_create_table_as(sql: str, filename: str, stdlib_path: str,
                                  allowlist: Dict[str, List[str]]) -> List[str]:
   errors = []
   for _, matches in match_pattern(CREATE_TABLE_AS_PATTERN, sql).items():
     name = matches[0]
-    if filename not in allowlist:
-      errors.append(f"CREATE TABLE '{name}' is deprecated."
-                    "Use CREATE PERFETTO TABLE instead.\n"
-                    f"Offending file: {filename}\n")
-      continue
-    if name not in allowlist[filename]:
-      errors.append(
-          f"Table '{name}' uses CREATE TABLE which is deprecated "
-          "and this table is not allowlisted. Use CREATE PERFETTO TABLE.\n"
-          f"Offending file: {filename}\n")
-  return errors
-
-
-# Given SQL string check whether there is (not allowlisted) usage of
-# CREATE TABLE {name} AS.
-def check_banned_create_table_as(sql: str, filename: str,
-                                 allowlist: Dict[str, List[str]]) -> List[str]:
-  errors = []
-  for _, matches in match_pattern(CREATE_TABLE_AS_PATTERN, sql).items():
-    name = matches[0]
-    if filename not in allowlist:
+    # Normalize paths before checking presence in the allowlist so it will
+    # work on Windows for the Chrome stdlib presubmit.
+    allowlist_normpath = dict(
+        (os.path.normpath(path), tables) for path, tables in allowlist.items())
+    allowlist_key = os.path.normpath(filename[len(stdlib_path):])
+    if allowlist_key not in allowlist_normpath:
       errors.append(f"CREATE TABLE '{name}' is deprecated. "
                     "Use CREATE PERFETTO TABLE instead.\n"
                     f"Offending file: {filename}\n")
       continue
-    if name not in allowlist[filename]:
+    if name not in allowlist_normpath[allowlist_key]:
       errors.append(
           f"Table '{name}' uses CREATE TABLE which is deprecated "
           "and this table is not allowlisted. Use CREATE PERFETTO TABLE.\n"
@@ -215,3 +205,13 @@
                   "Use CREATE PERFETTO VIEW instead.\n"
                   f"Offending file: {filename}\n")
   return errors
+
+
+# Given SQL string check whether there is usage of CREATE VIEW {name} AS.
+def check_banned_include_all(sql: str, filename: str) -> List[str]:
+  errors = []
+  for _, matches in match_pattern(INCLUDE_ALL_PATTERN, sql).items():
+    errors.append(
+        f"INCLUDE PERFETTO MODULE with wildcards is not allowed in stdlib. "
+        f"Import specific modules instead. Offending file: {filename}")
+  return errors
diff --git a/python/perfetto/prebuilts/manifests/trace_processor_shell.py b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
index b31da6e..61ff73c 100755
--- a/python/perfetto/prebuilts/manifests/trace_processor_shell.py
+++ b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v38.0
+# This file has been generated by: tools/roll-prebuilts v39.0
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9682976,
+        9814280,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-amd64/trace_processor_shell',
     'sha256':
-        '74b097836f16d788edce11bfda46f52e6499d8ec546d10f8dbab182612407b3b',
+        'd3b61b97f2e18aa8e6ece06b3167a012d23f895a97ed12e1f400e3f0480b72af',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8180008,
+        8295768,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-arm64/trace_processor_shell',
     'sha256':
-        '9a96a2f9ef81f210fcba4a08b21db6f2f57fb1d325e91924043ae066327c29a8',
+        '32560ee9eb8d86397fd8daec24e5e1e091e760c8cec708a1340cc7259cfd8f07',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9533320,
+        9660152,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-amd64/trace_processor_shell',
     'sha256':
-        'ee0ccae766aad09f0135efa83cc3a1cd78bd0e86824a7b1245e013d32e61d820',
+        'efbbb42291eecd0bd658694c6e60f3c7df5eee8b6e23ca6a1438059ddbd65667',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6963584,
+        7066080,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm/trace_processor_shell',
     'sha256':
-        '2c69d2016dd18b6d42bf6c2a5b4398e29e4fd294950882387942272dd25a045a',
+        'c62db9a9be13fefc114301e5a42714e400201bbf3a90d4f35c46494fc91ac230',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8950112,
+        9069000,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm64/trace_processor_shell',
     'sha256':
-        'a10a7a9a6614461beb1f32ee16da3290e2770bb6f3a506269defbdbf7e8803af',
+        'f2f5c4de02d9bd8505bcea931de8605d93485c5479130835e49b3d6ecf23fc4a',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,55 +75,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6580152,
+        6707128,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm/trace_processor_shell',
     'sha256':
-        '994a038b932accf5796550207a4e7d80c7612fd25e6eee414324e7e4f3ae14f2'
+        '5a9a5b8965f7b923444cb3ec9dea10df322a22d82765335be3191314aea48bf5'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8115560,
+        8234344,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm64/trace_processor_shell',
     'sha256':
-        '42426b12ad60894aee37e502e4d023cfded53e706d990abf5c70c4556c2b73ff'
+        'e6033a8928bc7b3ef6a5b4e56040219824dbba858b101241eb03513b39868829'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9009020,
+        9148284,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x86/trace_processor_shell',
     'sha256':
-        'af982ad7897d8cafb29a9f1303e32df20486a92eb07930db8b1898a75e84a667'
+        '38348a3295540e3c10d6622910d42844f37d7e849d3fbd8910b734df5c09f817'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9270696,
+        9397672,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x64/trace_processor_shell',
     'sha256':
-        '081be392ddccdf37da80dd888277ea9215cfa8d05ddf6f90d9bff13a0a72f672'
+        '2f91b3c32dd48891c0979c546074b208ea7c0aa46b8ef543835d8361cb962070'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        8898560,
+        9078272,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        '2e66e5b6ab9c0f7ecad98c3b5b822133c9aa34f137b4007c228c78672fdea5a7',
+        '65e2e29e2b6c76388af17f268cf5e597896a6e135a0bbbd224174f25715f0a32',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/python/perfetto/prebuilts/manifests/tracebox.py b/python/perfetto/prebuilts/manifests/tracebox.py
index 16ce884..8721e41 100755
--- a/python/perfetto/prebuilts/manifests/tracebox.py
+++ b/python/perfetto/prebuilts/manifests/tracebox.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v38.0
+# This file has been generated by: tools/roll-prebuilts v39.0
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1498560,
+        1498680,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-amd64/tracebox',
     'sha256':
-        'b760c7ed682d23f8d268174a939f1b8cb130ffb0d52f42b4cc4499a25423e782',
+        '07285ce963cb77212e580fe7034f6c380933c982a31272de47c7e0311d9144a1',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1376008,
+        1376136,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-arm64/tracebox',
     'sha256':
-        '2835127a5fc42e501e29a685a1cbcd98c26f977ee845bb1bd60a43008fd48161',
+        'f56e47303fde2de737d73bc0aebddb81a624a1130ab79ca22fb9aa4f41a4e662',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2212088,
+        2218840,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-amd64/tracebox',
     'sha256':
-        '0b4a61a3e45f4e1b6111ca0b440f9e4a0b0726df912542373f69b6821a3113bc',
+        '6a23cdbae7ecdf77b2c1f72b33350c6e4a2098627454cff4af42c027808e2be8',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1327204,
+        1332292,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm/tracebox',
     'sha256':
-        '43c20d80e5b40cebe5b26579319be6d57796b04155ffc90f133b6a13c2de25ab',
+        '4732def5d23a3c66c3c286387488c9043d4f68d535d22af8374c3e1b9a9a61f5',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2139928,
+        2147464,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm64/tracebox',
     'sha256':
-        '261cd7912dd69e8d82ce61b66a78f098eba46f6f492f6ec6bf73124d40a9a202',
+        'c9e83bc7b3d9059d3444392e509f96cbb242d45b0a0a27594a6b371c1a85e67f',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,42 +75,42 @@
     'file_name':
         'tracebox',
     'file_size':
-        1202132,
+        1230804,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm/tracebox',
     'sha256':
-        '43874c4d187e3da5820ae3988d16608a8bf89d04282b8ab3e1486c6e57cd891f'
+        '2def9ce29483c8e368cc7aa3e49ff2da60555444e6cb25c5acaec7c570b06f57'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'tracebox',
     'file_size':
-        1825448,
+        1854120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm64/tracebox',
     'sha256':
-        '9264d8f23ea3988f2952696668d3d572669134e4ca5b3e25a3209d3bfdb7d015'
+        '2484ee8210620a1dd9044610b75874302245d12aca7b59f6b7c3d55fe7ebcdde'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'tracebox',
     'file_size':
-        1820588,
+        1853356,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x86/tracebox',
     'sha256':
-        'd591e903b1e2b0e4b270f0510c24992aac68167c80e61450ebf6c36cc780cd21'
+        '7b9f8f8eb98343bf645582439c4deb9785e6b63724ff9470a703e3bf7e2522c2'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'tracebox',
     'file_size':
-        2108072,
+        2149032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x64/tracebox',
     'sha256':
-        'd3e4278e17764b605236c568202eb8d94f3f7e7593b0be9eba09f670a987acba'
+        'f81afa4516b96c3ff66cfe65b9c64567771efeef3bcb2b518a6846d358c0b93c'
 }]
diff --git a/python/perfetto/prebuilts/manifests/traceconv.py b/python/perfetto/prebuilts/manifests/traceconv.py
index 7065064..1b60f70 100755
--- a/python/perfetto/prebuilts/manifests/traceconv.py
+++ b/python/perfetto/prebuilts/manifests/traceconv.py
@@ -1,15 +1,15 @@
-# This file has been generated by: tools/roll-prebuilts v38.0
+# This file has been generated by: tools/roll-prebuilts v39.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        8889568,
+        9004496,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-amd64/traceconv',
     'sha256':
-        'e45d08f7050553f77c87764823c6ca26e877d0b3865f2764b33dcab6dfd0b9ee',
+        '11586dade97edf07d5b10fd5248127507fca16537f595d6530b81cbc266d1b12',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7447928,
+        7563688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-arm64/traceconv',
     'sha256':
-        '19cabe8789566e6383632bbe79e360b8bf05465f8bb156788c8c957aba77f224',
+        '48568604b4ad119f88d847d9b306102f47a5760b923ca574dfb08b3f49bdf253',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8633416,
+        8757640,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-amd64/traceconv',
     'sha256':
-        'a7a309c667a3e6a4c0268252540e3fc8052ebbd0abc30faf8f1783df411cf559',
+        'b263b7d4a63c923aad46940bde87063a647bb506667d857336f379d858d20646',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6505268,
+        6605348,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm/traceconv',
     'sha256':
-        '3ff0f9d4ef43e9e76d27c69827e508f4504d2d1fcfb4dac9d739023cd72106d7',
+        'c9454182a9e53b563511e1625533644829933cec13839a6fd3f08677a0e927c0',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8108432,
+        8224984,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm64/traceconv',
     'sha256':
-        '1f4641b341f2c8bf36faf8d4befedc01bd414c8f778a5b314c99c2d42c42c068',
+        '78cd7bfa0507c43c6ab728b691c5b885e18097d761c608a31388d3f8f7d84857',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,55 +75,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6096120,
+        6231288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm/traceconv',
     'sha256':
-        'f15c86b1d83bd1706b81426a6d11c3274044b4838abfb5d313954c65282162a9'
+        '87f57efb365dab3b5f4230518198cf0505476a6a8376842d41403ff0e7aa13ec'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7401672,
+        7528648,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm64/traceconv',
     'sha256':
-        '8e98e30a8e137116e74390bcb9d662f0a3f22f9d38fe3fd7ef305d1051bdf19d'
+        '896d7af7632179a05ad4278482ec00ede09806d6d4c5e07d2cc7a802bc672b5b'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8238268,
+        8393916,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x86/traceconv',
     'sha256':
-        '8ce56de90b18cc01d94b26f521292d71326552f8b4fb29b919f4b2b80c3eb2ec'
+        '6ce14e83af5a4e93d8dc7d39635ccb1cd7fffe988541b94960e167d33b5c5281'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8397056,
+        8528128,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x64/traceconv',
     'sha256':
-        'e5f31569261b3f201e2cc13b8eea510627d555767272373d9196e83ddaefa3f0'
+        '11aeb6293736175c09aacff155f793eb5508ab627f612cdecf7fec7e60485d78'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        7867904,
+        8034304,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/windows-amd64/traceconv.exe',
     'sha256':
-        '74c24b2a74ae2a490ac5f918ab079a7713af07e9a36e70a2cc8536040d7df099',
+        'd53e20465cf326b5d9f5f3513cc5cd387889024ed940ec243f56dcf6fe178279',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index b989e77..1fb3d89 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index 8b8383f..ecb561d 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/base/paged_memory_unittest.cc b/src/base/paged_memory_unittest.cc
index 7e3508b..388b5b2 100644
--- a/src/base/paged_memory_unittest.cc
+++ b/src/base/paged_memory_unittest.cc
@@ -35,14 +35,14 @@
 
 TEST(PagedMemoryTest, Basic) {
   const size_t kNumPages = 10;
-  const size_t kSize = 4096 * kNumPages;
+  const size_t kSize = GetSysPageSize() * kNumPages;
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
   void* ptr_raw = nullptr;
 #endif
   {
     PagedMemory mem = PagedMemory::Allocate(kSize);
     ASSERT_TRUE(mem.IsValid());
-    ASSERT_EQ(0u, reinterpret_cast<uintptr_t>(mem.Get()) % 4096);
+    ASSERT_EQ(0u, reinterpret_cast<uintptr_t>(mem.Get()) % GetSysPageSize());
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
     ptr_raw = mem.Get();
 #endif
@@ -95,8 +95,8 @@
 }
 
 TEST(PagedMemoryTest, Uncommitted) {
-  constexpr size_t kNumPages = 4096;
-  constexpr size_t kSize = 4096 * kNumPages;
+  const size_t kNumPages = 4096;
+  const size_t kSize = GetSysPageSize() * kNumPages;
 #if !PERFETTO_BUILDFLAG(PERFETTO_OS_FUCHSIA)
   char* ptr_raw = nullptr;
 #endif
@@ -156,14 +156,14 @@
 TEST(PagedMemoryTest, AccessUncommittedMemoryTriggersASAN) {
   EXPECT_DEATH_IF_SUPPORTED(
       {
-        constexpr size_t kNumPages = 4096;
-        constexpr size_t kSize = 4096 * kNumPages;
+        const size_t kNumPages = 2000;
+        const size_t kSize = GetSysPageSize() * kNumPages;
         PagedMemory mem =
             PagedMemory::Allocate(kSize, PagedMemory::kDontCommit);
         ASSERT_TRUE(mem.IsValid());
         char* ptr_raw = reinterpret_cast<char*>(mem.Get());
         // Only the first 1024 pages are mapped.
-        constexpr size_t kMappedSize = 4096 * 1024;
+        const size_t kMappedSize = GetSysPageSize() * 1024;
         ptr_raw[kMappedSize] = 'x';
         abort();
       },
diff --git a/src/base/thread_utils.cc b/src/base/thread_utils.cc
index 086c587..5e18bdb 100644
--- a/src/base/thread_utils.cc
+++ b/src/base/thread_utils.cc
@@ -56,8 +56,9 @@
 bool MaybeSetThreadName(const std::string& name) {
   // The SetThreadDescription API works even if no debugger is attached.
   static auto set_thread_description_func =
-      reinterpret_cast<SetThreadDescription>(::GetProcAddress(
-          ::GetModuleHandleA("Kernel32.dll"), "SetThreadDescription"));
+      reinterpret_cast<SetThreadDescription>(
+          reinterpret_cast<void*>(::GetProcAddress(
+              ::GetModuleHandleA("Kernel32.dll"), "SetThreadDescription")));
   if (!set_thread_description_func) {
     return false;
   }
@@ -72,8 +73,9 @@
 
 bool GetThreadName(std::string& out_result) {
   static auto get_thread_description_func =
-      reinterpret_cast<GetThreadDescription>(::GetProcAddress(
-          ::GetModuleHandleA("Kernel32.dll"), "GetThreadDescription"));
+      reinterpret_cast<GetThreadDescription>(
+          reinterpret_cast<void*>(::GetProcAddress(
+              ::GetModuleHandleA("Kernel32.dll"), "GetThreadDescription")));
   if (!get_thread_description_func) {
     return false;
   }
diff --git a/src/base/utils.cc b/src/base/utils.cc
index 82386fa..ab3f390 100644
--- a/src/base/utils.cc
+++ b/src/base/utils.cc
@@ -136,6 +136,34 @@
 namespace perfetto {
 namespace base {
 
+namespace internal {
+
+std::atomic<uint32_t> g_cached_page_size{0};
+
+uint32_t GetSysPageSizeSlowpath() {
+  uint32_t page_size = 0;
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+  const int page_size_int = getpagesize();
+  // If sysconf() fails for obscure reasons (e.g. SELinux denial) assume the
+  // page size is 4KB. This is to avoid regressing subtle SDK usages, as old
+  // versions of this code had a static constant baked in.
+  page_size = static_cast<uint32_t>(page_size_int > 0 ? page_size_int : 4096);
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+  page_size = static_cast<uint32_t>(vm_page_size);
+#else
+  page_size = 4096;
+#endif
+
+  PERFETTO_CHECK(page_size > 0 && page_size % 4096 == 0);
+
+  // Races here are fine because any thread will write the same value.
+  g_cached_page_size.store(page_size, std::memory_order_relaxed);
+  return page_size;
+}
+
+}  // namespace internal
+
 void MaybeReleaseAllocatorMemToOS() {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   // mallopt() on Android requires SDK level 26. Many targets and embedders
@@ -153,27 +181,6 @@
 #endif
 }
 
-uint32_t GetSysPageSize() {
-  ignore_result(kPageSize);  // Just to keep the amalgamated build happy.
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
-    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-  static std::atomic<uint32_t> page_size{0};
-  // This function might be called in hot paths. Avoid calling getpagesize() all
-  // the times, in many implementations getpagesize() calls sysconf() which is
-  // not cheap.
-  uint32_t cached_value = page_size.load(std::memory_order_relaxed);
-  if (PERFETTO_UNLIKELY(cached_value == 0)) {
-    cached_value = static_cast<uint32_t>(getpagesize());
-    page_size.store(cached_value, std::memory_order_relaxed);
-  }
-  return cached_value;
-#elif PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
-  return static_cast<uint32_t>(vm_page_size);
-#else
-  return 4096;
-#endif
-}
-
 uid_t GetCurrentUserId() {
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) ||   \
     PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 323d7bc..2667354 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -600,6 +600,7 @@
   trace_config_.reset(new TraceConfig());
 
   bool parsed = false;
+  bool cfg_could_be_txt = false;
   const bool will_trace_or_trigger = !is_attach() && !query_service_;
   if (!will_trace_or_trigger) {
     if ((!trace_config_raw.empty() || has_config_options)) {
@@ -625,6 +626,14 @@
                                      trace_config_.get());
     } else {
       parsed = trace_config_->ParseFromString(trace_config_raw);
+      cfg_could_be_txt =
+          !parsed && std::all_of(trace_config_raw.begin(),
+                                 trace_config_raw.end(), [](char c) {
+                                   // This is equiv to: isprint(c) || isspace(x)
+                                   // but doesn't depend on and load the locale.
+                                   return (c >= 32 && c <= 126) ||
+                                          (c >= 9 && c <= 13);
+                                 });
     }
   }
 
@@ -633,6 +642,12 @@
     trace_config_raw.clear();
   } else if (will_trace_or_trigger && !clone_tsid_) {
     PERFETTO_ELOG("The trace config is invalid, bailing out.");
+    if (cfg_could_be_txt) {
+      PERFETTO_ELOG(
+          "Looks like you are passing a textual config but I'm expecting a "
+          "proto-encoded binary config.");
+      PERFETTO_ELOG("Try adding --txt to the cmdline.");
+    }
     return 1;
   }
 
diff --git a/src/profiling/memory/shared_ring_buffer.cc b/src/profiling/memory/shared_ring_buffer.cc
index 32d9b3c..871ea21 100644
--- a/src/profiling/memory/shared_ring_buffer.cc
+++ b/src/profiling/memory/shared_ring_buffer.cc
@@ -41,18 +41,21 @@
 
 namespace {
 
-constexpr auto kMetaPageSize = base::kPageSize;
 constexpr auto kAlignment = 8;  // 64 bits to use aligned memcpy().
 constexpr auto kHeaderSize = kAlignment;
-constexpr auto kGuardSize = base::kPageSize * 1024 * 16;  // 64 MB.
+constexpr auto kGuardSize = 1024 * 1024 * 64;  // 64 MB.
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
 constexpr auto kFDSeals = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL;
 #endif
 
+size_t meta_page_size() {
+  return base::GetSysPageSize();
+}
+
 }  // namespace
 
 SharedRingBuffer::SharedRingBuffer(CreateFlag, size_t size) {
-  size_t size_with_meta = size + kMetaPageSize;
+  size_t size_with_meta = size + meta_page_size();
   base::ScopedFile fd;
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   bool is_memfd = false;
@@ -101,7 +104,7 @@
                 "MetadataPage must be trivially destructible");
 
   if (is_valid()) {
-    size_t outer_size = kMetaPageSize + size_ * 2 + kGuardSize;
+    size_t outer_size = meta_page_size() + size_ * 2 + kGuardSize;
     munmap(meta_, outer_size);
   }
 
@@ -139,19 +142,19 @@
     return;
   }
   auto size_with_meta = static_cast<size_t>(stat_buf.st_size);
-  auto size = size_with_meta - kMetaPageSize;
+  auto size = size_with_meta - meta_page_size();
 
   // |size_with_meta| must be a power of two number of pages + 1 page (for
   // metadata).
-  if (size_with_meta < 2 * base::kPageSize || size % base::kPageSize ||
-      (size & (size - 1))) {
+  if (size_with_meta < 2 * base::GetSysPageSize() ||
+      size % base::GetSysPageSize() || (size & (size - 1))) {
     PERFETTO_ELOG("SharedRingBuffer size is invalid (%zu)", size_with_meta);
     return;
   }
 
   // First of all reserve the whole virtual region to fit the buffer twice
   // + metadata page + red zone at the end.
-  size_t outer_size = kMetaPageSize + size * 2 + kGuardSize;
+  size_t outer_size = meta_page_size() + size * 2 + kGuardSize;
   uint8_t* region = reinterpret_cast<uint8_t*>(
       mmap(nullptr, outer_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
   if (region == MAP_FAILED) {
@@ -167,7 +170,7 @@
   // [ METADATA ] [ RING BUFFER SHMEM ] [ RING BUFFER SHMEM ]
   void* reg2 = mmap(region + size_with_meta, size, PROT_READ | PROT_WRITE,
                     MAP_SHARED | MAP_FIXED, *mem_fd,
-                    /*offset=*/kMetaPageSize);
+                    /*offset=*/static_cast<ssize_t>(meta_page_size()));
 
   if (reg1 != region || reg2 != region + size_with_meta) {
     PERFETTO_PLOG("mmap(MAP_SHARED) failed");
@@ -176,7 +179,7 @@
   }
   set_size(size);
   meta_ = reinterpret_cast<MetadataPage*>(region);
-  mem_ = region + kMetaPageSize;
+  mem_ = region + meta_page_size();
   mem_fd_ = std::move(mem_fd);
 }
 
diff --git a/src/profiling/memory/shared_ring_buffer.h b/src/profiling/memory/shared_ring_buffer.h
index 9bc2e26..2a49da5 100644
--- a/src/profiling/memory/shared_ring_buffer.h
+++ b/src/profiling/memory/shared_ring_buffer.h
@@ -266,7 +266,7 @@
 
   base::ScopedFile mem_fd_;
   MetadataPage* meta_ = nullptr;  // Start of the mmaped region.
-  uint8_t* mem_ = nullptr;  // Start of the contents (i.e. meta_ + kPageSize).
+  uint8_t* mem_ = nullptr;  // Start of the contents (i.e. meta_ + pagesize).
 
   // Size of the ring buffer contents, without including metadata or the 2nd
   // mmap.
diff --git a/src/profiling/memory/shared_ring_buffer_fuzzer.cc b/src/profiling/memory/shared_ring_buffer_fuzzer.cc
index 2a597a9..46990a0 100644
--- a/src/profiling/memory/shared_ring_buffer_fuzzer.cc
+++ b/src/profiling/memory/shared_ring_buffer_fuzzer.cc
@@ -20,6 +20,7 @@
 
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/temp_file.h"
+#include "perfetto/ext/base/utils.h"
 #include "src/profiling/memory/shared_ring_buffer.h"
 
 namespace perfetto {
@@ -55,7 +56,7 @@
   size_t payload_size = size - sizeof(SharedRingBuffer::MetadataPage);
   const uint8_t* payload = data + sizeof(SharedRingBuffer::MetadataPage);
   size_t payload_size_pages =
-      (payload_size + base::kPageSize - 1) / base::kPageSize;
+      (payload_size + base::GetSysPageSize() - 1) / base::GetSysPageSize();
   // Upsize test buffer to be 2^n data pages (precondition of the impl) + 1 page
   // for the metadata.
   size_t total_size_pages = 1 + RoundToPow2(payload_size_pages);
@@ -68,9 +69,10 @@
   header.spinlock.poisoned = false;
 
   PERFETTO_CHECK(ftruncate(*fd, static_cast<off_t>(total_size_pages *
-                                                   base::kPageSize)) == 0);
+                                                   base::GetSysPageSize())) ==
+                 0);
   PERFETTO_CHECK(base::WriteAll(*fd, &header, sizeof(header)) != -1);
-  PERFETTO_CHECK(lseek(*fd, base::kPageSize, SEEK_SET) != -1);
+  PERFETTO_CHECK(lseek(*fd, base::GetSysPageSize(), SEEK_SET) != -1);
   PERFETTO_CHECK(base::WriteAll(*fd, payload, payload_size) != -1);
 
   auto buf = SharedRingBuffer::Attach(std::move(fd));
diff --git a/src/profiling/memory/shared_ring_buffer_unittest.cc b/src/profiling/memory/shared_ring_buffer_unittest.cc
index ff533f3..5128b7a 100644
--- a/src/profiling/memory/shared_ring_buffer_unittest.cc
+++ b/src/profiling/memory/shared_ring_buffer_unittest.cc
@@ -101,7 +101,7 @@
     ASSERT_EQ(ToString(buf_and_size), data);
     rd->EndRead(std::move(buf_and_size));
   }
-  data = std::string(base::kPageSize - sizeof(uint64_t), '#');
+  data = std::string(base::GetSysPageSize() - sizeof(uint64_t), '#');
   for (int i = 0; i < 4; i++)
     ASSERT_TRUE(TryWrite(wr, data.data(), data.size()));
 
@@ -146,7 +146,7 @@
 }
 
 TEST(SharedRingBufferTest, ReadShutdown) {
-  constexpr auto kBufSize = base::kPageSize * 4;
+  const size_t kBufSize = base::GetSysPageSize() * 4;
   std::optional<SharedRingBuffer> wr = SharedRingBuffer::Create(kBufSize);
   ASSERT_TRUE(wr);
   SharedRingBuffer rd =
@@ -157,7 +157,7 @@
 }
 
 TEST(SharedRingBufferTest, WriteShutdown) {
-  constexpr auto kBufSize = base::kPageSize * 4;
+  const size_t kBufSize = base::GetSysPageSize() * 4;
   std::optional<SharedRingBuffer> rd = SharedRingBuffer::Create(kBufSize);
   ASSERT_TRUE(rd);
   SharedRingBuffer wr =
@@ -173,13 +173,13 @@
 }
 
 TEST(SharedRingBufferTest, SingleThreadSameInstance) {
-  constexpr auto kBufSize = base::kPageSize * 4;
+  const size_t kBufSize = base::GetSysPageSize() * 4;
   std::optional<SharedRingBuffer> buf = SharedRingBuffer::Create(kBufSize);
   StructuredTest(&*buf, &*buf);
 }
 
 TEST(SharedRingBufferTest, SingleThreadAttach) {
-  constexpr auto kBufSize = base::kPageSize * 4;
+  const size_t kBufSize = base::GetSysPageSize() * 4;
   std::optional<SharedRingBuffer> buf1 = SharedRingBuffer::Create(kBufSize);
   std::optional<SharedRingBuffer> buf2 =
       SharedRingBuffer::Attach(base::ScopedFile(dup(buf1->fd())));
@@ -187,7 +187,7 @@
 }
 
 TEST(SharedRingBufferTest, MultiThreadingTest) {
-  constexpr auto kBufSize = base::kPageSize * 1024;  // 4 MB
+  const size_t kBufSize = base::GetSysPageSize() * 1024;  // 4 MB
   SharedRingBuffer rd = *SharedRingBuffer::Create(kBufSize);
   SharedRingBuffer wr =
       *SharedRingBuffer::Attach(base::ScopedFile(dup(rd.fd())));
@@ -201,7 +201,7 @@
     while (!writers_enabled.load()) {
     }
     std::minstd_rand0 rnd_engine(static_cast<uint32_t>(thread_id));
-    std::uniform_int_distribution<size_t> dist(1, base::kPageSize * 8);
+    std::uniform_int_distribution<size_t> dist(1, base::GetSysPageSize() * 8);
     for (int i = 0; i < 1000; i++) {
       size_t size = dist(rnd_engine);
       ASSERT_GT(size, 0u);
@@ -255,13 +255,13 @@
 }
 
 TEST(SharedRingBufferTest, InvalidSize) {
-  constexpr auto kBufSize = base::kPageSize * 4 + 1;
+  const size_t kBufSize = base::GetSysPageSize() * 4 + 1;
   std::optional<SharedRingBuffer> wr = SharedRingBuffer::Create(kBufSize);
   EXPECT_EQ(wr, std::nullopt);
 }
 
 TEST(SharedRingBufferTest, EmptyWrite) {
-  constexpr auto kBufSize = base::kPageSize * 4;
+  const size_t kBufSize = base::GetSysPageSize() * 4;
   std::optional<SharedRingBuffer> wr = SharedRingBuffer::Create(kBufSize);
   ASSERT_TRUE(wr);
   SharedRingBuffer::Buffer buf;
diff --git a/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc b/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc
index 62b82eb..fa81338 100644
--- a/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc
+++ b/src/profiling/memory/shared_ring_buffer_write_fuzzer.cc
@@ -20,6 +20,7 @@
 
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/temp_file.h"
+#include "perfetto/ext/base/utils.h"
 #include "src/profiling/memory/shared_ring_buffer.h"
 
 namespace perfetto {
@@ -60,7 +61,7 @@
   size_t payload_size = size - sizeof(FuzzingInputHeader);
   const uint8_t* payload = data + sizeof(FuzzingInputHeader);
   size_t payload_size_pages =
-      (payload_size + base::kPageSize - 1) / base::kPageSize;
+      (payload_size + base::GetSysPageSize() - 1) / base::GetSysPageSize();
   // Upsize test buffer to be 2^n data pages (precondition of the impl) + 1 page
   // for the metadata.
   size_t total_size_pages = 1 + RoundToPow2(payload_size_pages);
@@ -73,10 +74,11 @@
   metadata_page.spinlock.poisoned = false;
 
   PERFETTO_CHECK(ftruncate(*fd, static_cast<off_t>(total_size_pages *
-                                                   base::kPageSize)) == 0);
+                                                   base::GetSysPageSize())) ==
+                 0);
   PERFETTO_CHECK(base::WriteAll(*fd, &metadata_page, sizeof(metadata_page)) !=
                  -1);
-  PERFETTO_CHECK(lseek(*fd, base::kPageSize, SEEK_SET) != -1);
+  PERFETTO_CHECK(lseek(*fd, base::GetSysPageSize(), SEEK_SET) != -1);
   PERFETTO_CHECK(base::WriteAll(*fd, payload, payload_size) != -1);
 
   auto buf = SharedRingBuffer::Attach(std::move(fd));
diff --git a/src/profiling/perf/event_config.cc b/src/profiling/perf/event_config.cc
index 25b05c3..79df2ce 100644
--- a/src/profiling/perf/event_config.cc
+++ b/src/profiling/perf/event_config.cc
@@ -416,7 +416,7 @@
     // TODO(rsavitski): for now, make an extremely conservative guess of an 8
     // byte sample (stack sampling samples can be up to 64KB). This is most
     // likely as good as no limit in practice.
-    samples_per_tick_limit = *ring_buffer_pages * (base::kPageSize / 8);
+    samples_per_tick_limit = *ring_buffer_pages * (base::GetSysPageSize() / 8);
   }
   PERFETTO_DLOG("Capping samples (not records) per tick to [%" PRIu64 "]",
                 samples_per_tick_limit);
diff --git a/src/profiling/perf/event_reader.cc b/src/profiling/perf/event_reader.cc
index 2a40c6c..cb606f6 100644
--- a/src/profiling/perf/event_reader.cc
+++ b/src/profiling/perf/event_reader.cc
@@ -119,8 +119,8 @@
   PerfRingBuffer ret;
 
   // mmap request is one page larger than the buffer size (for the metadata).
-  ret.data_buf_sz_ = data_page_count * base::kPageSize;
-  ret.mmap_sz_ = ret.data_buf_sz_ + base::kPageSize;
+  ret.data_buf_sz_ = data_page_count * base::GetSysPageSize();
+  ret.mmap_sz_ = ret.data_buf_sz_ + base::GetSysPageSize();
 
   // If PROT_WRITE, kernel won't overwrite unread samples.
   void* mmap_addr = mmap(nullptr, ret.mmap_sz_, PROT_READ | PROT_WRITE,
@@ -132,8 +132,8 @@
 
   // Expected layout is [ metadata page ] [ data pages ... ]
   ret.metadata_page_ = reinterpret_cast<perf_event_mmap_page*>(mmap_addr);
-  ret.data_buf_ = reinterpret_cast<char*>(mmap_addr) + base::kPageSize;
-  PERFETTO_CHECK(ret.metadata_page_->data_offset == base::kPageSize);
+  ret.data_buf_ = reinterpret_cast<char*>(mmap_addr) + base::GetSysPageSize();
+  PERFETTO_CHECK(ret.metadata_page_->data_offset == base::GetSysPageSize());
   PERFETTO_CHECK(ret.metadata_page_->data_size == ret.data_buf_sz_);
 
   PERFETTO_DCHECK(IsPowerOfTwo(ret.data_buf_sz_));
diff --git a/src/protozero/protoc_plugin/cppgen_plugin.cc b/src/protozero/protoc_plugin/cppgen_plugin.cc
index a33803f..f134caa 100644
--- a/src/protozero/protoc_plugin/cppgen_plugin.cc
+++ b/src/protozero/protoc_plugin/cppgen_plugin.cc
@@ -27,11 +27,7 @@
 #include <google/protobuf/compiler/code_generator.h>
 #include <google/protobuf/compiler/importer.h>
 #include <google/protobuf/compiler/plugin.h>
-#include <google/protobuf/dynamic_message.h>
 #include <google/protobuf/io/printer.h>
-#include <google/protobuf/io/zero_copy_stream_impl.h>
-#include <google/protobuf/util/field_comparator.h>
-#include <google/protobuf/util/message_differencer.h>
 
 #include "perfetto/ext/base/string_utils.h"
 
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index a2e64da..d6c5589 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -126,6 +126,7 @@
     "startup/launches_minsdk33.sql",
     "startup/mcycles_per_launch.sql",
     "startup/slice_functions.sql",
+    "startup/slow_start_reasons.sql",
     "startup/system_state.sql",
     "startup/thread_state_breakdown.sql",
     "unsymbolized_frames.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql
index b14e5cb..cc401b6 100644
--- a/src/trace_processor/metrics/sql/android/android_startup.sql
+++ b/src/trace_processor/metrics/sql/android/android_startup.sql
@@ -25,6 +25,9 @@
 SELECT RUN_METRIC('android/startup/slice_functions.sql');
 INCLUDE PERFETTO MODULE common.timestamps;
 
+-- Define helper functions related to slow start reasons
+SELECT RUN_METRIC('android/startup/slow_start_reasons.sql');
+
 -- Run all the HSC metrics.
 SELECT RUN_METRIC('android/startup/hsc.sql');
 
@@ -338,6 +341,8 @@
       'dex2oat_dur_ns',
       dur_of_process_running_concurrent_to_launch(launches.startup_id, '*dex2oat64')
     ),
+    -- Remove slow_start_reason implementation once slow_start_reason_detailed
+    -- is added to slow_start dashboards. (b/308460401)
     'slow_start_reason', (SELECT RepeatedField(slow_cause)
       FROM (
         SELECT 'No baseline or cloud profiles' AS slow_cause
@@ -481,11 +486,12 @@
         SELECT 'Main Thread - Binder transactions blocked'
         WHERE (
           SELECT COUNT(1)
-          FROM BINDER_TRANSACTION_REPLY_SLICES_FOR_LAUNCH(launches.startup_id, 2e7)
+          FROM binder_transaction_reply_slices_for_launch(launches.startup_id, 2e7)
         ) > 0
 
       )
-    )
+    ),
+    'slow_start_reason_detailed', get_slow_start_reason_detailed(launches.startup_id)
   ) AS startup
 FROM android_startups launches;
 
diff --git a/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql b/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql
new file mode 100644
index 0000000..e9b1393
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/startup/slow_start_reasons.sql
@@ -0,0 +1,391 @@
+--
+-- Copyright 2022 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
+--
+--     https://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.
+--
+
+INCLUDE PERFETTO MODULE android.startup.startups;
+
+SELECT RUN_METRIC('android/startup/thread_state_breakdown.sql');
+SELECT RUN_METRIC('android/startup/system_state.sql');
+SELECT RUN_METRIC('android/startup/mcycles_per_launch.sql');
+
+CREATE OR REPLACE PERFETTO FUNCTION get_percent(num LONG, total LONG)
+RETURNS STRING AS
+  SELECT SUBSTRING(CAST(($num * 100 + 0.0) / $total AS STRING), 1, 5);
+
+CREATE OR REPLACE PERFETTO FUNCTION get_ns_to_s(ns LONG)
+RETURNS STRING AS
+  SELECT CAST(($ns + 0.0) / 1e9 AS STRING);
+
+CREATE OR REPLACE PERFETTO FUNCTION get_ns_to_ms(ns LONG)
+RETURNS STRING AS
+  SELECT SUBSTRING(CAST(($ns + 0.0) / 1e6 AS STRING), 1, 6);
+
+CREATE OR REPLACE PERFETTO FUNCTION get_longest_chunk(start_ns LONG, dur_ns LONG, tid LONG, name STRING)
+RETURNS STRING AS
+  SELECT " [ longest_chunk:"
+    || " start_s " || get_ns_to_s($start_ns - TRACE_START())
+    || " dur_ms " || get_ns_to_ms($dur_ns)
+    || " thread_id " || $tid
+    || " thread_name " || $name
+    || " ]";
+
+CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_time_for_launch_in_runnable_state(
+  startup_id LONG, launches_dur LONG)
+RETURNS STRING AS
+  SELECT
+    " target" || " 15%"
+    || " actual "
+    || get_percent(main_thread_time_for_launch_in_runnable_state($startup_id), $launches_dur)
+    || "%"
+    || get_longest_chunk(ts, dur, tid, name)
+    || " [ extra_info: "
+    || " launches_dur_ms " || get_ns_to_ms($launches_dur)
+    || " runnable_dur_ms "
+    || get_ns_to_ms(main_thread_time_for_launch_in_runnable_state($startup_id))
+    || " R_sum_dur_ms "
+    || get_ns_to_ms(IFNULL(main_thread_time_for_launch_and_state($startup_id, "R"), 0))
+    || " R+(Preempted)_sum_dur_ms "
+    || get_ns_to_ms(IFNULL(main_thread_time_for_launch_and_state($startup_id, "R+"), 0))
+    || " ]"
+  FROM launch_threads_by_thread_state l
+  JOIN thread USING (utid)
+  WHERE l.startup_id = $startup_id AND (state GLOB "R" OR state GLOB "R+") AND l.is_main_thread
+  ORDER BY dur DESC
+  LIMIT 1;
+
+CREATE OR REPLACE PERFETTO FUNCTION get_android_sum_dur_on_main_thread_for_startup_and_slice(
+  startup_id LONG, slice_name STRING, launches_dur LONG)
+RETURNS STRING AS
+  SELECT
+    " target" || " 20%"
+    || " actual "
+    || get_percent(android_sum_dur_on_main_thread_for_startup_and_slice(
+          $startup_id, $slice_name), $launches_dur) || "%"
+    || get_longest_chunk(slice_ts, slice_dur, tid, name)
+    || " [ extra_info: "
+    || " launches_dur_ms " || get_ns_to_ms($launches_dur)
+    || " sum_dur_ms "
+    || get_ns_to_ms(android_sum_dur_on_main_thread_for_startup_and_slice($startup_id, $slice_name))
+    || " ]"
+    FROM android_thread_slices_for_all_startups slices
+    JOIN thread USING (utid)
+    WHERE startup_id = $startup_id AND slice_name GLOB $slice_name AND slices.is_main_thread
+    ORDER BY slice_dur DESC
+    LIMIT 1;
+
+CREATE OR REPLACE PERFETTO FUNCTION get_android_sum_dur_for_startup_and_slice(
+  startup_id LONG, slice_name STRING, target_ms LONG)
+RETURNS STRING AS
+  SELECT
+    " target " || $target_ms || "ms"
+    || " actual "
+    || get_ns_to_ms(android_sum_dur_for_startup_and_slice($startup_id, $slice_name)) || "ms"
+    || get_longest_chunk(slice_ts, slice_dur, tid, name)
+    FROM android_thread_slices_for_all_startups
+    JOIN thread USING (utid)
+    WHERE startup_id = $startup_id AND slice_name GLOB $slice_name
+    ORDER BY slice_dur DESC
+    LIMIT 1;
+
+CREATE OR REPLACE PERFETTO FUNCTION get_potential_cpu_contention_with_another_process(startup_id LONG)
+RETURNS STRING AS
+  SELECT
+    " target" || " 100ms"
+    || " actual "
+    || get_ns_to_ms(main_thread_time_for_launch_in_runnable_state($startup_id)) || "ms"
+    || " most_active_process_for_launch " || most_active_process_for_launch($startup_id)
+    || get_longest_chunk(ts, dur, tid, name)
+    || " [ extra_info: "
+    || " runnable_dur_ms "
+    || get_ns_to_ms(main_thread_time_for_launch_in_runnable_state($startup_id))
+    || " R_sum_dur_ms "
+    || get_ns_to_ms(IFNULL(main_thread_time_for_launch_and_state($startup_id, "R"), 0))
+    || " R+(Preempted)_sum_dur "
+    || IFNULL(main_thread_time_for_launch_and_state($startup_id, "R+"), 0)
+    || " ]"
+  FROM launch_threads_by_thread_state l
+  JOIN thread USING (utid)
+  WHERE l.startup_id = $startup_id AND (state GLOB "R" OR state GLOB "R+") AND l.is_main_thread
+  ORDER BY dur DESC
+  LIMIT 1;
+
+CREATE OR REPLACE PERFETTO FUNCTION get_jit_activity(startup_id LONG)
+RETURNS STRING AS
+  SELECT
+    " target" || " 100ms"
+    || " actual "
+    || get_ns_to_ms(thread_time_for_launch_state_and_thread(
+      $startup_id, 'Running', 'Jit thread pool'))
+    || "ms"
+    || get_longest_chunk(ts, dur, tid, name)
+  FROM launch_threads_by_thread_state l
+  JOIN thread USING (utid)
+  WHERE l.startup_id = $startup_id AND state GLOB 'Running' AND thread_name = 'Jit thread pool'
+  ORDER BY dur DESC
+  LIMIT 1;
+
+CREATE OR REPLACE PERFETTO FUNCTION get_main_thread_binder_transactions_blocked(
+  startup_id LONG, threshold DOUBLE)
+RETURNS STRING AS
+  SELECT
+    " per_instance_target" || " 20ms"
+    || " per_instance_actual " || get_ns_to_ms(request.slice_dur) || "ms"
+    || get_longest_chunk(request.slice_ts, request.slice_dur, tid, request.thread_name)
+    || " [ extra_info: "
+    || " reply.dur_ms " || get_ns_to_ms(reply.dur)
+    || " ]"
+  FROM (
+    SELECT slice_id as id, slice_dur, thread_name, process.name as process,
+      s.arg_set_id, is_main_thread,
+      slice_ts, s.utid
+    FROM android_thread_slices_for_all_startups s
+    JOIN process ON (
+      EXTRACT_ARG(s.arg_set_id, "destination process") = process.pid
+    )
+    WHERE startup_id = $startup_id AND slice_name GLOB "binder transaction"
+      AND slice_dur > $threshold
+  ) request
+  JOIN following_flow(request.id) arrow
+  JOIN slice reply ON reply.id = arrow.slice_in
+  JOIN thread USING (utid)
+  WHERE reply.dur > $threshold AND request.is_main_thread
+  ORDER BY request.slice_dur DESC
+  LIMIT 1;
+
+CREATE OR REPLACE PERFETTO FUNCTION get_missing_baseline_profile_for_launch(
+  startup_id LONG, pkg_name STRING)
+RETURNS STRING AS
+  SELECT
+    " target " || "FALSE"
+    || " actual " || "TRUE"
+    || get_longest_chunk(slice_ts, slice_dur, -1, thread_name)
+    || " [ extra_info: "
+    || " slice_name " || slice_name
+    || " ]"
+    FROM (
+      SELECT *
+      FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(
+        $startup_id,
+        "location=* status=* filter=* reason=*"
+      )
+      ORDER BY slice_name
+    )
+    WHERE
+      -- when location is the package odex file and the reason is "install" or "install-dm",
+      -- if the compilation filter is not "speed-profile", baseline/cloud profile is missing.
+      SUBSTR(STR_SPLIT(slice_name, " status=", 0), LENGTH("location=") + 1)
+        GLOB ("*" || $pkg_name || "*odex")
+      AND (STR_SPLIT(slice_name, " reason=", 1) = "install"
+        OR STR_SPLIT(slice_name, " reason=", 1) = "install-dm")
+    ORDER BY slice_dur DESC
+    LIMIT 1;
+
+
+CREATE PERFETTO FUNCTION get_slow_start_reason_detailed(startup_id LONG)
+RETURNS PROTO AS
+      SELECT RepeatedField(AndroidStartupMetric_SlowStartReasonDetailed(
+        'reason', slow_cause,
+        'details', details))
+      FROM (
+        SELECT 'No baseline or cloud profiles' AS slow_cause,
+          get_missing_baseline_profile_for_launch(launch.startup_id, launch.package) as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND missing_baseline_profile_for_launch(launch.startup_id, launch.package)
+
+        UNION ALL
+        SELECT 'Optimized artifacts missing, run from apk' as slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND  run_from_apk_for_launch(launch.startup_id)
+
+        UNION ALL
+        SELECT 'Unlock running during launch' as slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+         AND is_unlock_running_during_launch(launch.startup_id)
+
+        UNION ALL
+        SELECT 'App in debuggable mode' as slow_cause, NULL as details
+       	FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND is_process_debuggable(launch.package)
+
+        UNION ALL
+        SELECT 'GC Activity' as slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND total_gc_time_by_launch(launch.startup_id) > 0
+
+        UNION ALL
+        SELECT 'dex2oat running during launch' AS slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id AND
+          dur_of_process_running_concurrent_to_launch(launch.startup_id, '*dex2oat64') > 0
+
+        UNION ALL
+        SELECT 'installd running during launch' AS slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id AND
+          dur_of_process_running_concurrent_to_launch(launch.startup_id, '*installd') > 0
+
+        UNION ALL
+        SELECT 'Main Thread - Time spent in Runnable state' as slow_cause,
+          get_main_thread_time_for_launch_in_runnable_state(
+            launch.startup_id, launch.dur) as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND main_thread_time_for_launch_in_runnable_state(launch.startup_id) > launch.dur * 0.15
+
+        UNION ALL
+        SELECT 'Main Thread - Time spent in interruptible sleep state'
+          AS slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND main_thread_time_for_launch_and_state(launch.startup_id, 'S') > 2900e6
+
+        UNION ALL
+        SELECT 'Main Thread - Time spent in Blocking I/O' as slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND main_thread_time_for_launch_state_and_io_wait(launch.startup_id, 'D*', TRUE) > 450e6
+
+        UNION ALL
+        SELECT 'Main Thread - Time spent in OpenDexFilesFromOat*' as slow_cause,
+          get_android_sum_dur_on_main_thread_for_startup_and_slice(
+            launch.startup_id, 'OpenDexFilesFromOat*', launch.dur) as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id AND
+          android_sum_dur_on_main_thread_for_startup_and_slice(
+          launch.startup_id, 'OpenDexFilesFromOat*') > launch.dur * 0.2
+
+        UNION ALL
+        SELECT 'Time spent in bindApplication'
+          AS slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND android_sum_dur_for_startup_and_slice(launch.startup_id, 'bindApplication') > 1250e6
+
+        UNION ALL
+        SELECT 'Time spent in view inflation' as slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND android_sum_dur_for_startup_and_slice(launch.startup_id, 'inflate') > 450e6
+
+        UNION ALL
+        SELECT 'Time spent in ResourcesManager#getResources' as slow_cause,
+          get_android_sum_dur_for_startup_and_slice(
+            launch.startup_id, 'ResourcesManager#getResources', 130) as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND android_sum_dur_for_startup_and_slice(
+          launch.startup_id, 'ResourcesManager#getResources') > 130e6
+
+        UNION ALL
+        SELECT 'Time spent verifying classes'
+          AS slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id AND
+          android_sum_dur_for_startup_and_slice(launch.startup_id, 'VerifyClass*')
+            > launch.dur * 0.15
+
+        UNION ALL
+        SELECT 'Potential CPU contention with another process' AS slow_cause,
+          get_potential_cpu_contention_with_another_process(launch.startup_id) as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id AND
+          main_thread_time_for_launch_in_runnable_state(launch.startup_id) > 100e6 AND
+          most_active_process_for_launch(launch.startup_id) IS NOT NULL
+
+        UNION ALL
+        SELECT 'JIT Activity' as slow_cause,
+          get_jit_activity(launch.startup_id) as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+        AND thread_time_for_launch_state_and_thread(
+          launch.startup_id,
+          'Running',
+          'Jit thread pool'
+        ) > 100e6
+
+        UNION ALL
+        SELECT 'Main Thread - Lock contention'
+          AS slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND android_sum_dur_on_main_thread_for_startup_and_slice(
+          launch.startup_id,
+          'Lock contention on*'
+        ) > launch.dur * 0.2
+
+        UNION ALL
+        SELECT 'Main Thread - Monitor contention'
+          AS slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND android_sum_dur_on_main_thread_for_startup_and_slice(
+          launch.startup_id,
+          'Lock contention on a monitor*'
+        ) > launch.dur * 0.15
+
+        UNION ALL
+        SELECT 'JIT compiled methods' as slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND (
+          SELECT COUNT(1)
+          FROM ANDROID_SLICES_FOR_STARTUP_AND_SLICE_NAME(launch.startup_id, 'JIT compiling*')
+          WHERE thread_name = 'Jit thread pool'
+        ) > 65
+
+        UNION ALL
+        SELECT 'Broadcast dispatched count' as slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND count_slices_concurrent_to_launch(
+          launch.startup_id,
+          'Broadcast dispatched*'
+        ) > 15
+
+        UNION ALL
+        SELECT 'Broadcast received count' as slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND count_slices_concurrent_to_launch(
+          launch.startup_id,
+          'broadcastReceiveReg*'
+        ) > 50
+
+        UNION ALL
+        SELECT 'Startup running concurrent to launch' as slow_cause, NULL as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND EXISTS(
+          SELECT package
+          FROM android_startups l
+          WHERE l.startup_id != launch.startup_id
+            AND is_spans_overlapping(l.ts, l.ts_end, launch.ts, launch.ts_end)
+        )
+
+        UNION ALL
+        SELECT 'Main Thread - Binder transactions blocked' as slow_cause,
+          get_main_thread_binder_transactions_blocked(launch.startup_id, 2e7) as details
+        FROM android_startups launch
+        WHERE launch.startup_id = $startup_id
+          AND (
+          SELECT COUNT(1)
+          FROM BINDER_TRANSACTION_REPLY_SLICES_FOR_LAUNCH(launch.startup_id, 2e7)
+        ) > 0
+      );
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 ee342b1..9e8d747 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -259,8 +259,8 @@
       source = std::move(source_or.value());
     } else if (auto* cst = std::get_if<PerfettoSqlParser::CreateTable>(
                    &parser.statement())) {
-      RETURN_IF_ERROR(AddTracebackIfNeeded(
-          RegisterRuntimeTable(cst->name, cst->sql), parser.statement_sql()));
+      RETURN_IF_ERROR(AddTracebackIfNeeded(ExecuteCreateTable(*cst),
+                                           parser.statement_sql()));
       source = RewriteToDummySql(parser.statement_sql());
     } else if (auto* create_view = std::get_if<PerfettoSqlParser::CreateView>(
                    &parser.statement())) {
@@ -367,6 +367,12 @@
   auto* ctx = static_cast<CreatedFunction::Context*>(
       sqlite_engine()->GetFunctionContext(prototype.function_name,
                                           created_argc));
+  if (ctx && replace) {
+    // If the function already exists and we are replacing it, unregister it.
+    RETURN_IF_ERROR(UnregisterFunctionWithSqlite(
+        prototype.function_name.c_str(), created_argc));
+    ctx = nullptr;
+  }
   if (!ctx) {
     // We register the function with SQLite before we prepare the statement so
     // the statement can reference the function itself, enabling recursive
@@ -384,31 +390,19 @@
       std::move(return_type_str), std::move(sql));
 }
 
-base::Status PerfettoSqlEngine::RegisterRuntimeTable(std::string name,
-                                                     SqlSource sql) {
-  auto stmt_or = engine_->PrepareStatement(sql);
+base::Status PerfettoSqlEngine::ExecuteCreateTable(
+    const PerfettoSqlParser::CreateTable& create_table) {
+  auto stmt_or = engine_->PrepareStatement(create_table.sql);
   RETURN_IF_ERROR(stmt_or.status());
   SqliteEngine::PreparedStatement stmt = std::move(stmt_or);
 
-  uint32_t columns =
-      static_cast<uint32_t>(sqlite3_column_count(stmt.sqlite_stmt()));
-  std::vector<std::string> column_names;
-  for (uint32_t i = 0; i < columns; ++i) {
-    std::string col_name =
-        sqlite3_column_name(stmt.sqlite_stmt(), static_cast<int>(i));
-    if (col_name.empty()) {
-      return base::ErrStatus(
-          "CREATE PERFETTO TABLE: column name must not be empty");
-    }
-    if (!std::isalpha(col_name.front()) ||
-        !sql_argument::IsValidName(base::StringView(col_name))) {
-      return base::ErrStatus(
-          "Column name %s has to start with a letter and can only consists "
-          "of alphanumeric characters and underscores.",
-          col_name.c_str());
-    }
-    column_names.push_back(col_name);
-  }
+  base::StatusOr<std::vector<std::string>> maybe_column_names =
+      GetColumnNamesFromSelectStatement(stmt, "CREATE PERFETTO TABLE");
+  RETURN_IF_ERROR(maybe_column_names.status());
+  std::vector<std::string> column_names = *maybe_column_names;
+
+  RETURN_IF_ERROR(ValidateColumnNames(column_names, create_table.schema,
+                                      "CREATE PERFETTO TABLE"));
 
   size_t column_count = column_names.size();
   auto table = std::make_unique<RuntimeTable>(pool_, std::move(column_names));
@@ -440,19 +434,33 @@
           return base::ErrStatus(
               "CREATE PERFETTO TABLE on column '%s' in table '%s': bytes "
               "columns are not supported",
-              sqlite3_column_name(stmt.sqlite_stmt(), int_i), name.c_str());
+              sqlite3_column_name(stmt.sqlite_stmt(), int_i),
+              create_table.name.c_str());
       }
     }
   }
   if (res != SQLITE_DONE) {
     return base::ErrStatus("%s: SQLite error while creating table body: %s",
-                           name.c_str(), sqlite3_errmsg(engine_->db()));
+                           create_table.name.c_str(),
+                           sqlite3_errmsg(engine_->db()));
   }
   RETURN_IF_ERROR(table->AddColumnsAndOverlays(rows));
 
-  runtime_tables_.Insert(name, std::move(table));
+  if (runtime_tables_.Find(create_table.name)) {
+    if (!create_table.replace) {
+      return base::ErrStatus("CREATE PERFETTO TABLE: table '%s' already exists",
+                             create_table.name.c_str());
+    }
+
+    base::StackString<1024> drop("DROP TABLE %s", create_table.name.c_str());
+    RETURN_IF_ERROR(
+        Execute(SqlSource::FromTraceProcessorImplementation(drop.ToStdString()))
+            .status());
+  }
+
+  runtime_tables_.Insert(create_table.name, std::move(table));
   base::StackString<1024> create("CREATE VIRTUAL TABLE %s USING runtime_table",
-                                 name.c_str());
+                                 create_table.name.c_str());
   return Execute(
              SqlSource::FromTraceProcessorImplementation(create.ToStdString()))
       .status();
@@ -460,7 +468,30 @@
 
 base::Status PerfettoSqlEngine::ExecuteCreateView(
     const PerfettoSqlParser::CreateView& create_view) {
-  RETURN_IF_ERROR(Execute(create_view.sql).status());
+  // Verify that the underlying SQL statement is valid.
+  auto stmt = sqlite_engine()->PrepareStatement(create_view.select_sql);
+  RETURN_IF_ERROR(stmt.status());
+
+  if (create_view.replace) {
+    base::StackString<1024> drop_if_exists("DROP VIEW IF EXISTS %s",
+                                           create_view.name.c_str());
+    RETURN_IF_ERROR(Execute(SqlSource::FromTraceProcessorImplementation(
+                                drop_if_exists.ToStdString()))
+                        .status());
+  }
+
+  // If the schema is specified, verify that the column names match it.
+  if (!create_view.schema.empty()) {
+    base::StatusOr<std::vector<std::string>> maybe_column_names =
+        GetColumnNamesFromSelectStatement(stmt, "CREATE PERFETTO VIEW");
+    RETURN_IF_ERROR(maybe_column_names.status());
+    std::vector<std::string> column_names = *maybe_column_names;
+
+    RETURN_IF_ERROR(ValidateColumnNames(column_names, create_view.schema,
+                                        "CREATE PERFETTO VIEW"));
+  }
+
+  RETURN_IF_ERROR(Execute(create_view.create_view_sql).status());
   runtime_views_count_++;
   return base::OkStatus();
 }
@@ -483,23 +514,60 @@
   std::string key = include.key;
   PERFETTO_TP_TRACE(metatrace::Category::QUERY_TIMELINE, "Include",
                     [key](metatrace::Record* r) { r->AddArg("Module", key); });
-  std::string module_name = sql_modules::GetModuleName(key);
-  auto module = FindModule(module_name);
-  if (!module)
-    return base::ErrStatus("INCLUDE: Unknown module name provided - %s",
-                           key.c_str());
 
-  auto module_file = module->include_key_to_file.Find(key);
-  if (!module_file) {
-    return base::ErrStatus("INCLUDE: Unknown filename provided - %s",
-                           key.c_str());
-  }
-  // INCLUDE is noop for already included files.
-  if (module_file->included) {
+  if (key == "*") {
+    for (auto moduleIt = modules_.GetIterator(); moduleIt; ++moduleIt) {
+      RETURN_IF_ERROR(IncludeModuleImpl(moduleIt.value(), key, parser));
+    }
     return base::OkStatus();
   }
 
-  auto it = Execute(SqlSource::FromModuleInclude(module_file->sql, key));
+  std::string module_name = sql_modules::GetModuleName(key);
+  auto module = FindModule(module_name);
+  if (!module) {
+    return base::ErrStatus("INCLUDE: Unknown module name provided - %s",
+                           key.c_str());
+  }
+  return IncludeModuleImpl(*module, key, parser);
+}
+
+base::Status PerfettoSqlEngine::IncludeModuleImpl(
+    sql_modules::RegisteredModule& module,
+    const std::string& key,
+    const PerfettoSqlParser& parser) {
+  if (!key.empty() && key.back() == '*') {
+    // If the key ends with a wildcard, iterate through all the keys in the
+    // module and include matching ones.
+    std::string prefix = key.substr(0, key.size() - 1);
+    for (auto fileIt = module.include_key_to_file.GetIterator(); fileIt;
+         ++fileIt) {
+      if (!base::StartsWith(fileIt.key(), prefix))
+        continue;
+      PERFETTO_TP_TRACE(
+          metatrace::Category::QUERY_TIMELINE,
+          "Include (expanded from wildcard)",
+          [&](metatrace::Record* r) { r->AddArg("Module", fileIt.key()); });
+      RETURN_IF_ERROR(IncludeFileImpl(fileIt.value(), fileIt.key(), parser));
+    }
+    return base::OkStatus();
+  }
+  auto* module_file = module.include_key_to_file.Find(key);
+  if (!module_file) {
+    return base::ErrStatus("INCLUDE: unknown module '%s'", key.c_str());
+  }
+  return IncludeFileImpl(*module_file, key, parser);
+}
+
+base::Status PerfettoSqlEngine::IncludeFileImpl(
+    sql_modules::RegisteredModule::ModuleFile& file,
+    const std::string& key,
+    const PerfettoSqlParser& parser) {
+  // INCLUDE is noop for already included files.
+  if (file.included) {
+    return base::OkStatus();
+  }
+
+  auto it = Execute(SqlSource::FromModuleInclude(file.sql, key));
   if (!it.status().ok()) {
     return base::ErrStatus("%s%s",
                            parser.statement_sql().AsTraceback(0).c_str(),
@@ -507,8 +575,7 @@
   }
   if (it->statement_count_with_output > 0)
     return base::ErrStatus("INCLUDE: Included module returning values.");
-  module_file->included = true;
-
+  file.included = true;
   return base::OkStatus();
 }
 
@@ -684,5 +751,96 @@
   PERFETTO_CHECK(runtime_table_fn_states_.Erase(base::ToLower(name)));
 }
 
+base::Status PerfettoSqlEngine::UnregisterFunctionWithSqlite(const char* name,
+                                                             int argc) {
+  return engine_->UnregisterFunction(name, argc);
+}
+
+base::StatusOr<std::vector<std::string>>
+PerfettoSqlEngine::GetColumnNamesFromSelectStatement(
+    const SqliteEngine::PreparedStatement& stmt,
+    const char* tag) const {
+  uint32_t columns =
+      static_cast<uint32_t>(sqlite3_column_count(stmt.sqlite_stmt()));
+  std::vector<std::string> column_names;
+  for (uint32_t i = 0; i < columns; ++i) {
+    std::string col_name =
+        sqlite3_column_name(stmt.sqlite_stmt(), static_cast<int>(i));
+    if (col_name.empty()) {
+      return base::ErrStatus("%s: column %d: name must not be empty", tag, i);
+    }
+    if (!std::isalpha(col_name.front())) {
+      return base::ErrStatus(
+          "%s: Column %i: name '%s' has to start with a letter.", tag, i,
+          col_name.c_str());
+    }
+    if (!sql_argument::IsValidName(base::StringView(col_name))) {
+      return base::ErrStatus(
+          "%s: Column %i: name '%s' has to contain only alphanumeric "
+          "characters and underscores.",
+          tag, i, col_name.c_str());
+    }
+    column_names.push_back(col_name);
+  }
+  return column_names;
+}
+
+base::Status PerfettoSqlEngine::ValidateColumnNames(
+    const std::vector<std::string>& column_names,
+    const std::vector<sql_argument::ArgumentDefinition>& schema,
+    const char* tag) const {
+  // If the user has not provided a schema, we have nothing to validate.
+  if (schema.empty()) {
+    return base::OkStatus();
+  }
+
+  std::vector<std::string> columns_missing_from_query;
+  std::vector<std::string> columns_missing_from_schema;
+
+  for (const std::string& name : column_names) {
+    bool present =
+        std::find_if(schema.begin(), schema.end(), [&name](const auto& arg) {
+          return arg.name() == base::StringView(name);
+        }) != schema.end();
+    if (!present) {
+      columns_missing_from_schema.push_back(name);
+    }
+  }
+
+  for (const auto& arg : schema) {
+    bool present = std::find_if(column_names.begin(), column_names.end(),
+                                [&arg](const std::string& name) {
+                                  return arg.name() == base::StringView(name);
+                                }) != column_names.end();
+    if (!present) {
+      columns_missing_from_query.push_back(arg.name().ToStdString());
+    }
+  }
+
+  if (columns_missing_from_query.empty() &&
+      columns_missing_from_schema.empty()) {
+    return base::OkStatus();
+  }
+
+  if (columns_missing_from_query.empty()) {
+    return base::ErrStatus(
+        "%s: the following columns are missing from the schema: %s", tag,
+        base::Join(columns_missing_from_schema, ", ").c_str());
+  }
+
+  if (columns_missing_from_schema.empty()) {
+    return base::ErrStatus(
+        "%s: the following columns are declared in the schema, but do not "
+        "exist: %s",
+        tag, base::Join(columns_missing_from_query, ", ").c_str());
+  }
+
+  return base::ErrStatus(
+      "%s: the following columns are declared in the schema, but do not exist: "
+      "%s; and the folowing columns exist, but are not declared: %s",
+      tag, base::Join(columns_missing_from_query, ", ").c_str(),
+      base::Join(columns_missing_from_schema, ", ").c_str());
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
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 bb34a08..6a8d2d0 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.h
@@ -132,6 +132,7 @@
   // Makes new SQL module available to import.
   void RegisterModule(const std::string& name,
                       sql_modules::RegisteredModule module) {
+    modules_.Erase(name);
     modules_.Insert(name, std::move(module));
   }
 
@@ -163,8 +164,9 @@
   base::Status ExecuteInclude(const PerfettoSqlParser::Include&,
                               const PerfettoSqlParser& parser);
 
-  // Registers a SQL-defined trace processor C++ table with SQLite.
-  base::Status RegisterRuntimeTable(std::string name, SqlSource sql);
+  // Creates a runtime table and registers it with SQLite.
+  base::Status ExecuteCreateTable(
+      const PerfettoSqlParser::CreateTable& create_table);
 
   base::Status ExecuteCreateView(const PerfettoSqlParser::CreateView&);
 
@@ -177,6 +179,34 @@
       std::unique_ptr<typename Function::Context> ctx,
       bool deterministic = true);
 
+  base::Status UnregisterFunctionWithSqlite(const char* name, int argc);
+
+  // Get the column names from a statement.
+  // |operator_name| is used in the error message if the statement is invalid.
+  base::StatusOr<std::vector<std::string>> GetColumnNamesFromSelectStatement(
+      const SqliteEngine::PreparedStatement& stmt,
+      const char* tag) const;
+
+  // Validates that the column names in |column_names| match the |schema|.
+  // |operator_name| is used in the error message if the statement is invalid.
+  base::Status ValidateColumnNames(
+      const std::vector<std::string>& column_names,
+      const std::vector<sql_argument::ArgumentDefinition>& schema,
+      const char* operator_name) const;
+
+  // Given a module and a key, include the correct file(s) from the module.
+  // The key can contain a wildcard to include all files in the module with the
+  // matching prefix.
+  base::Status IncludeModuleImpl(sql_modules::RegisteredModule& module,
+                                 const std::string& key,
+                                 const PerfettoSqlParser& parser);
+
+  // Import a given file.
+  base::Status IncludeFileImpl(
+      sql_modules::RegisteredModule::ModuleFile& module,
+      const std::string& key,
+      const PerfettoSqlParser& parser);
+
   std::unique_ptr<QueryCache> query_cache_;
   StringPool* pool_ = nullptr;
 
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc
index 141ff51..784ef34 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine_unittest.cc
@@ -29,29 +29,46 @@
   PerfettoSqlEngine engine_{&pool_};
 };
 
-TEST_F(PerfettoSqlEngineTest, CreatePerfettoFunctionSmoke) {
+sql_modules::RegisteredModule CreateTestModule(
+    std::vector<std::pair<std::string, std::string>> files) {
+  sql_modules::RegisteredModule result;
+  for (auto& file : files) {
+    result.include_key_to_file[file.first] =
+        sql_modules::RegisteredModule::ModuleFile{file.second, false};
+  }
+  return result;
+}
+
+// These are the smoke tests for the perfetto SQL engine, focusing on
+// ensuring that the correct statements do not return an error and that
+// incorrect statements do.
+//
+// Functional tests are covered by the diff tests in
+// test/trace_processor/diff_tests/syntax/perfetto_sql.
+
+TEST_F(PerfettoSqlEngineTest, Function_Create) {
   auto res = engine_.Execute(SqlSource::FromExecuteQuery(
       "CREATE PERFETTO FUNCTION foo() RETURNS INT AS select 1"));
-  ASSERT_TRUE(res.ok());
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
 
   res = engine_.Execute(
       SqlSource::FromExecuteQuery("creatE PeRfEttO FUNCTION foo(x INT, y LONG) "
                                   "RETURNS INT AS select :x + :y"));
-  ASSERT_TRUE(res.ok());
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
 }
 
-TEST_F(PerfettoSqlEngineTest, CreatePerfettoFunctionArgs) {
+TEST_F(PerfettoSqlEngineTest, Function_CreateWithArgs) {
   auto res = engine_.ExecuteUntilLastStatement(
       SqlSource::FromExecuteQuery("creatE PeRfEttO FUNCTION foo(x INT, y LONG) "
                                   "RETURNS INT AS select $x + $y;"
                                   "SELECT foo(1, 2)"));
-  ASSERT_TRUE(res.ok());
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
   ASSERT_FALSE(res->stmt.IsDone());
   ASSERT_EQ(sqlite3_column_int64(res->stmt.sqlite_stmt(), 0), 3);
   ASSERT_FALSE(res->stmt.Step());
 }
 
-TEST_F(PerfettoSqlEngineTest, CreatePerfettoFunctionError) {
+TEST_F(PerfettoSqlEngineTest, Function_Invalid) {
   auto res = engine_.ExecuteUntilLastStatement(
       SqlSource::FromExecuteQuery("creatE PeRfEttO FUNCTION foo(x INT, y LONG) "
                                   "AS select $x + $y;"
@@ -59,44 +76,32 @@
   ASSERT_FALSE(res.ok());
 }
 
-TEST_F(PerfettoSqlEngineTest, CreatePerfettoTableSmoke) {
+TEST_F(PerfettoSqlEngineTest, Function_Duplicates) {
   auto res = engine_.Execute(SqlSource::FromExecuteQuery(
-      "CREATE PERFETTO TABLE foo AS SELECT 42 AS bar"));
-  ASSERT_TRUE(res.ok());
+      "CREATE PERFETTO FUNCTION foo() RETURNS INT AS SELECT 1"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+
+  res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO FUNCTION foo() RETURNS INT AS SELECT 2"));
+  ASSERT_FALSE(res.ok());
+
+  res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE OR REPLACE PERFETTO FUNCTION foo() RETURNS INT AS SELECT 3"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
 }
 
-TEST_F(PerfettoSqlEngineTest, CreatePerfettoTableStringSmoke) {
-  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
-      "CREATE PERFETTO TABLE foo AS SELECT 'foo' AS bar"));
-  ASSERT_TRUE(res.ok());
-}
-
-TEST_F(PerfettoSqlEngineTest, CreatePerfettoTableDrop) {
-  auto res_create = engine_.Execute(SqlSource::FromExecuteQuery(
-      "CREATE PERFETTO TABLE foo AS SELECT 'foo' AS bar"));
-  ASSERT_TRUE(res_create.ok());
-
-  auto res_drop =
-      engine_.Execute(SqlSource::FromExecuteQuery("DROP TABLE foo"));
-  ASSERT_TRUE(res_drop.ok());
-}
-
-TEST_F(PerfettoSqlEngineTest, CreatePerfettoTableValues) {
-  auto res = engine_.ExecuteUntilLastStatement(
-      SqlSource::FromExecuteQuery("creatE PeRfEttO TABLE foo AS "
-                                  "SELECT 42 as bar;"
-                                  "SELECT * from foo"));
-  ASSERT_TRUE(res.ok());
-  ASSERT_FALSE(res->stmt.IsDone());
-  ASSERT_EQ(sqlite3_column_int64(res->stmt.sqlite_stmt(), 0), 42);
-  ASSERT_FALSE(res->stmt.Step());
-}
-
-TEST_F(PerfettoSqlEngineTest, CreateTableFunctionDupe) {
+TEST_F(PerfettoSqlEngineTest, TableFunction_Create) {
   auto res = engine_.Execute(SqlSource::FromExecuteQuery(
       "CREATE PERFETTO FUNCTION foo() RETURNS TABLE(x INT) AS "
       "select 1 AS x"));
-  ASSERT_TRUE(res.ok());
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+}
+
+TEST_F(PerfettoSqlEngineTest, TableFunction_Duplicates) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO FUNCTION foo() RETURNS TABLE(x INT) AS "
+      "select 1 AS x"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
 
   res = engine_.Execute(SqlSource::FromExecuteQuery(
       "CREATE PERFETTO FUNCTION foo() RETURNS TABLE(x INT) AS "
@@ -106,26 +111,117 @@
   res = engine_.Execute(SqlSource::FromExecuteQuery(
       "CREATE OR REPLACE PERFETTO FUNCTION foo() RETURNS TABLE(x INT) AS "
       "select 2 AS x"));
-  ASSERT_TRUE(res.ok());
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
 }
 
-TEST_F(PerfettoSqlEngineTest, CreatePerfettoViewSmoke) {
+TEST_F(PerfettoSqlEngineTest, Table_Create) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo AS SELECT 42 AS bar"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+}
+
+TEST_F(PerfettoSqlEngineTest, Table_StringColumns) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo AS SELECT 'foo' AS bar"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+}
+
+TEST_F(PerfettoSqlEngineTest, Table_Schema) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo(bar INT) AS SELECT 42 AS bar"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+
+  res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo2(bar INT) AS SELECT 42 AS bar; SELECT 1"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+}
+
+TEST_F(PerfettoSqlEngineTest, Table_IncorrectSchema) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo(x INT) AS SELECT 1 as y"));
+  ASSERT_FALSE(res.ok());
+  EXPECT_THAT(
+      res.status().c_message(),
+      testing::EndsWith("CREATE PERFETTO TABLE: the following columns are "
+                        "declared in the schema, but do not exist: x; and the "
+                        "folowing columns exist, but are not declared: y"));
+}
+
+TEST_F(PerfettoSqlEngineTest, Table_Drop) {
+  auto res_create = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo AS SELECT 'foo' AS bar"));
+  ASSERT_TRUE(res_create.ok());
+
+  auto res_drop =
+      engine_.Execute(SqlSource::FromExecuteQuery("DROP TABLE foo"));
+  ASSERT_TRUE(res_drop.ok());
+}
+
+TEST_F(PerfettoSqlEngineTest, Table_Duplicates) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo AS SELECT 1 as bar"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+
+  res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO TABLE foo AS SELECT 1 as bar"));
+  ASSERT_FALSE(res.ok());
+
+  res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE OR REPLACE PERFETTO TABLE foo AS SELECT 1 as bar"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+}
+
+TEST_F(PerfettoSqlEngineTest, View_Create) {
   auto res = engine_.Execute(SqlSource::FromExecuteQuery(
       "CREATE PERFETTO VIEW foo AS SELECT 42 AS bar"));
-  ASSERT_TRUE(res.ok());
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
 }
 
-TEST_F(PerfettoSqlEngineTest, CreatePerfettoViewWithSchemaSmoke) {
+TEST_F(PerfettoSqlEngineTest, View_Schema) {
   auto res = engine_.Execute(SqlSource::FromExecuteQuery(
       "CREATE PERFETTO VIEW foo(bar INT) AS SELECT 42 AS bar"));
-  ASSERT_TRUE(res.ok());
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
 
   res = engine_.Execute(SqlSource::FromExecuteQuery(
       "CREATE PERFETTO VIEW foo2(bar INT) AS SELECT 42 AS bar; SELECT 1"));
-  ASSERT_TRUE(res.ok());
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
 }
 
-TEST_F(PerfettoSqlEngineTest, CreateMacro) {
+TEST_F(PerfettoSqlEngineTest, View_Drop) {
+  auto res_create = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo AS SELECT 'foo' AS bar"));
+  ASSERT_TRUE(res_create.ok());
+
+  auto res_drop = engine_.Execute(SqlSource::FromExecuteQuery("DROP VIEW foo"));
+  ASSERT_TRUE(res_drop.ok());
+}
+
+TEST_F(PerfettoSqlEngineTest, View_IncorrectSchema) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo(x INT) AS SELECT 1 as y"));
+  ASSERT_FALSE(res.ok());
+  EXPECT_THAT(
+      res.status().c_message(),
+      testing::EndsWith("CREATE PERFETTO VIEW: the following columns are "
+                        "declared in the schema, but do not exist: x; and the "
+                        "folowing columns exist, but are not declared: y"));
+}
+
+TEST_F(PerfettoSqlEngineTest, View_Duplicates) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo AS SELECT 1 as bar"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+
+  res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO VIEW foo AS SELECT 1 as bar"));
+  ASSERT_FALSE(res.ok());
+
+  res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE OR REPLACE PERFETTO VIEW foo AS SELECT 1 as bar"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+}
+
+TEST_F(PerfettoSqlEngineTest, Macro_Create) {
   auto res_create = engine_.Execute(SqlSource::FromExecuteQuery(
       "CREATE PERFETTO MACRO foo() RETURNS TableOrSubquery AS select 42 AS x"));
   ASSERT_TRUE(res_create.ok()) << res_create.status().c_message();
@@ -143,6 +239,61 @@
   ASSERT_FALSE(res->stmt.Step());
 }
 
+TEST_F(PerfettoSqlEngineTest, Macro_Duplicates) {
+  auto res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO MACRO foo() RETURNS TableOrSubquery AS select 42 AS x"));
+  ASSERT_TRUE(res.ok()) << res.status().c_message();
+
+  res = engine_.Execute(SqlSource::FromExecuteQuery(
+      "CREATE PERFETTO MACRO foo() RETURNS TableOrSubquery AS select 42 AS x"));
+  ASSERT_FALSE(res.ok());
+
+  res = engine_.Execute(
+      SqlSource::FromExecuteQuery("CREATE OR REPLACE PERFETTO MACRO foo() "
+                                  "RETURNS TableOrSubquery AS select 42 AS x"));
+  ASSERT_TRUE(res.ok());
+}
+
+TEST_F(PerfettoSqlEngineTest, Include_All) {
+  engine_.RegisterModule(
+      "foo", CreateTestModule(
+                 {{"foo.foo", "CREATE PERFETTO TABLE foo AS SELECT 42 AS x"}}));
+  engine_.RegisterModule(
+      "bar",
+      CreateTestModule(
+          {{"bar.bar", "CREATE PERFETTO TABLE bar AS SELECT 42 AS x "}}));
+
+  auto res_create =
+      engine_.Execute(SqlSource::FromExecuteQuery("INCLUDE PERFETTO MODULE *"));
+  ASSERT_TRUE(res_create.ok()) << res_create.status().c_message();
+  ASSERT_TRUE(
+      engine_.FindModule("foo")->include_key_to_file["foo.foo"].included);
+  ASSERT_TRUE(
+      engine_.FindModule("bar")->include_key_to_file["bar.bar"].included);
+}
+
+TEST_F(PerfettoSqlEngineTest, Include_Module) {
+  engine_.RegisterModule(
+      "foo", CreateTestModule({
+                 {"foo.foo1", "CREATE PERFETTO TABLE foo1 AS SELECT 42 AS x"},
+                 {"foo.foo2", "CREATE PERFETTO TABLE foo2 AS SELECT 42 AS x"},
+             }));
+  engine_.RegisterModule(
+      "bar",
+      CreateTestModule(
+          {{"bar.bar", "CREATE PERFETTO TABLE bar AS SELECT 42 AS x "}}));
+
+  auto res_create = engine_.Execute(
+      SqlSource::FromExecuteQuery("INCLUDE PERFETTO MODULE foo.*"));
+  ASSERT_TRUE(res_create.ok()) << res_create.status().c_message();
+  ASSERT_TRUE(
+      engine_.FindModule("foo")->include_key_to_file["foo.foo1"].included);
+  ASSERT_TRUE(
+      engine_.FindModule("foo")->include_key_to_file["foo.foo2"].included);
+  ASSERT_FALSE(
+      engine_.FindModule("bar")->include_key_to_file["bar.bar"].included);
+}
+
 }  // namespace
 }  // namespace trace_processor
 }  // namespace perfetto
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 0666a6f..3063fc3 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.cc
@@ -80,7 +80,18 @@
 }
 
 bool ValidateModuleName(const std::string& name) {
+  if (name.empty()) {
+    return false;
+  }
+
   std::vector<std::string> packages = base::SplitString(name, ".");
+
+  // The last part of the path can be a wildcard.
+  if (!packages.empty() && packages.back() == "*") {
+    packages.pop_back();
+  }
+
+  // The rest of the path must be valid words.
   return std::find_if(packages.begin(), packages.end(),
                       std::not_fn(IsValidModuleWord)) == packages.end();
 }
@@ -190,21 +201,20 @@
         break;
       case State::kCreateOrReplacePerfetto:
       case State::kCreatePerfetto:
+        bool replace = state == State::kCreateOrReplacePerfetto;
         if (TokenIsCustomKeyword("function", token)) {
-          return ParseCreatePerfettoFunction(
-              state == State::kCreateOrReplacePerfetto, *first_non_space_token);
+          return ParseCreatePerfettoFunction(replace, *first_non_space_token);
         }
         if (TokenIsSqliteKeyword("table", token)) {
-          return ParseCreatePerfettoTableOrView(*first_non_space_token,
+          return ParseCreatePerfettoTableOrView(replace, *first_non_space_token,
                                                 TableOrView::kTable);
         }
         if (TokenIsSqliteKeyword("view", token)) {
-          return ParseCreatePerfettoTableOrView(*first_non_space_token,
+          return ParseCreatePerfettoTableOrView(replace, *first_non_space_token,
                                                 TableOrView::kView);
         }
         if (TokenIsCustomKeyword("macro", token)) {
-          return ParseCreatePerfettoMacro(state ==
-                                          State::kCreateOrReplacePerfetto);
+          return ParseCreatePerfettoMacro(replace);
         }
         base::StackString<1024> err(
             "Expected 'FUNCTION', 'TABLE' or 'MACRO' after 'CREATE PERFETTO', "
@@ -223,8 +233,8 @@
 
   if (!ValidateModuleName(key)) {
     base::StackString<1024> err(
-        "Only alphanumeric characters, dots and underscores allowed in include "
-        "keys: '%s'",
+        "Include key should be a dot-separated list of module names, with the"
+        "last name optionally being a wildcard: '%s'",
         key.c_str());
     return ErrorAtToken(tok, err.c_str());
   }
@@ -235,6 +245,7 @@
 }
 
 bool PerfettoSqlParser::ParseCreatePerfettoTableOrView(
+    bool replace,
     Token first_non_space_token,
     TableOrView table_or_view) {
   Token table_name = tokenizer_.NextNonWhitespace();
@@ -270,7 +281,7 @@
   Token terminal = tokenizer_.NextTerminal();
   switch (table_or_view) {
     case TableOrView::kTable:
-      statement_ = CreateTable{std::move(name),
+      statement_ = CreateTable{replace, std::move(name),
                                tokenizer_.Substr(first, terminal), schema};
       break;
     case TableOrView::kView:
@@ -281,8 +292,9 @@
       SqlSource::Rewriter rewriter(original_statement);
       tokenizer_.Rewrite(rewriter, first_non_space_token, first, header,
                          SqliteTokenizer::EndToken::kExclusive);
-      statement_ =
-          CreateView{std::move(name), std::move(rewriter).Build(), schema};
+      statement_ = CreateView{replace, std::move(name),
+                              tokenizer_.Substr(first, terminal),
+                              std::move(rewriter).Build(), schema};
       break;
   }
   statement_sql_ = tokenizer_.Substr(first_non_space_token, terminal);
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 b8120fb..012b55e 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_parser.h
@@ -61,6 +61,7 @@
   // Indicates that the specified SQL was a CREATE PERFETTO TABLE statement
   // with the following parameters.
   struct CreateTable {
+    bool replace;
     std::string name;
     // SQL source for the select statement.
     SqlSource sql;
@@ -69,10 +70,13 @@
   // Indicates that the specified SQL was a CREATE PERFETTO VIEW statement
   // with the following parameters.
   struct CreateView {
+    bool replace;
     std::string name;
+    // SQL source for the select statement.
+    SqlSource select_sql;
     // SQL source corresponding to the rewritten statement creating the
     // underlying view.
-    SqlSource sql;
+    SqlSource create_view_sql;
     std::vector<sql_argument::ArgumentDefinition> schema;
   };
   // Indicates that the specified SQL was a INCLUDE PERFETTO MODULE statement
@@ -152,6 +156,7 @@
     kView,
   };
   bool ParseCreatePerfettoTableOrView(
+      bool replace,
       SqliteTokenizer::Token first_non_space_token,
       TableOrView table_or_view);
 
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 7856964..e3653ca 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
@@ -131,6 +131,14 @@
                            "STRING", FindSubstr(res, "select 'foo'"), false}));
 }
 
+TEST_F(PerfettoSqlParserTest, CreateOrReplacePerfettoFunctionScalar) {
+  auto res = SqlSource::FromExecuteQuery(
+      "create or replace perfetto function foo() returns INT as select 1");
+  ASSERT_THAT(*Parse(res), testing::ElementsAre(CreateFn{
+                               true, FunctionPrototype{"foo", {}}, "INT",
+                               FindSubstr(res, "select 1"), false}));
+}
+
 TEST_F(PerfettoSqlParserTest, CreatePerfettoFunctionScalarError) {
   auto res = SqlSource::FromExecuteQuery(
       "create perfetto function foo( returns INT as select 1");
@@ -202,6 +210,19 @@
   ASSERT_FALSE(parser.Next());
 }
 
+TEST_F(PerfettoSqlParserTest, CreateOrReplacePerfettoMacro) {
+  auto res = SqlSource::FromExecuteQuery(
+      "create or replace perfetto macro foo() returns Expr as 1");
+  PerfettoSqlParser parser(res, macros_);
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(parser.statement(), Statement(CreateMacro{true,
+                                                      FindSubstr(res, "foo"),
+                                                      {},
+                                                      FindSubstr(res, "Expr"),
+                                                      FindSubstr(res, "1")}));
+  ASSERT_FALSE(parser.Next());
+}
+
 TEST_F(PerfettoSqlParserTest, CreatePerfettoMacroAndOther) {
   auto res = SqlSource::FromExecuteQuery(
       "create perfetto macro foo() returns sql1 as random sql snippet; "
@@ -226,9 +247,20 @@
       "CREATE PERFETTO TABLE foo AS SELECT 42 AS bar");
   PerfettoSqlParser parser(res, macros_);
   ASSERT_TRUE(parser.Next());
-  ASSERT_EQ(
-      parser.statement(),
-      Statement(CreateTable{"foo", FindSubstr(res, "SELECT 42 AS bar"), {}}));
+  ASSERT_EQ(parser.statement(),
+            Statement(CreateTable{
+                false, "foo", FindSubstr(res, "SELECT 42 AS bar"), {}}));
+  ASSERT_FALSE(parser.Next());
+}
+
+TEST_F(PerfettoSqlParserTest, CreateOrReplacePerfettoTable) {
+  auto res = SqlSource::FromExecuteQuery(
+      "CREATE OR REPLACE PERFETTO TABLE foo AS SELECT 42 AS bar");
+  PerfettoSqlParser parser(res, macros_);
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(parser.statement(),
+            Statement(CreateTable{
+                true, "foo", FindSubstr(res, "SELECT 42 AS bar"), {}}));
   ASSERT_FALSE(parser.Next());
 }
 
@@ -238,6 +270,7 @@
   PerfettoSqlParser parser(res, macros_);
   ASSERT_TRUE(parser.Next());
   ASSERT_EQ(parser.statement(), Statement(CreateTable{
+                                    false,
                                     "foo",
                                     FindSubstr(res, "SELECT 42 AS bar"),
                                     {{"$bar", sql_argument::Type::kInt}},
@@ -250,9 +283,9 @@
       "CREATE PERFETTO TABLE foo AS SELECT 42 AS bar; select 1");
   PerfettoSqlParser parser(res, macros_);
   ASSERT_TRUE(parser.Next());
-  ASSERT_EQ(
-      parser.statement(),
-      Statement(CreateTable{"foo", FindSubstr(res, "SELECT 42 AS bar"), {}}));
+  ASSERT_EQ(parser.statement(),
+            Statement(CreateTable{
+                false, "foo", FindSubstr(res, "SELECT 42 AS bar"), {}}));
   ASSERT_TRUE(parser.Next());
   ASSERT_EQ(parser.statement(), Statement(SqliteSql{}));
   ASSERT_EQ(parser.statement_sql(), FindSubstr(res, "select 1"));
@@ -264,11 +297,30 @@
       "CREATE PERFETTO VIEW foo AS SELECT 42 AS bar");
   PerfettoSqlParser parser(res, macros_);
   ASSERT_TRUE(parser.Next());
-  ASSERT_EQ(parser.statement(),
-            Statement(CreateView{"foo",
-                                 SqlSource::FromExecuteQuery(
-                                     "CREATE VIEW foo AS SELECT 42 AS bar"),
-                                 {}}));
+  ASSERT_EQ(
+      parser.statement(),
+      Statement(CreateView{
+          false,
+          "foo",
+          SqlSource::FromExecuteQuery("SELECT 42 AS bar"),
+          SqlSource::FromExecuteQuery("CREATE VIEW foo AS SELECT 42 AS bar"),
+          {}}));
+  ASSERT_FALSE(parser.Next());
+}
+
+TEST_F(PerfettoSqlParserTest, CreateOrReplacePerfettoView) {
+  auto res = SqlSource::FromExecuteQuery(
+      "CREATE OR REPLACE PERFETTO VIEW foo AS SELECT 42 AS bar");
+  PerfettoSqlParser parser(res, macros_);
+  ASSERT_TRUE(parser.Next());
+  ASSERT_EQ(
+      parser.statement(),
+      Statement(CreateView{
+          true,
+          "foo",
+          SqlSource::FromExecuteQuery("SELECT 42 AS bar"),
+          SqlSource::FromExecuteQuery("CREATE VIEW foo AS SELECT 42 AS bar"),
+          {}}));
   ASSERT_FALSE(parser.Next());
 }
 
@@ -277,11 +329,14 @@
       "CREATE PERFETTO VIEW foo AS SELECT 42 AS bar; select 1");
   PerfettoSqlParser parser(res, macros_);
   ASSERT_TRUE(parser.Next());
-  ASSERT_EQ(parser.statement(),
-            Statement(CreateView{"foo",
-                                 SqlSource::FromExecuteQuery(
-                                     "CREATE VIEW foo AS SELECT 42 AS bar"),
-                                 {}}));
+  ASSERT_EQ(
+      parser.statement(),
+      Statement(CreateView{
+          false,
+          "foo",
+          SqlSource::FromExecuteQuery("SELECT 42 AS bar"),
+          SqlSource::FromExecuteQuery("CREATE VIEW foo AS SELECT 42 AS bar"),
+          {}}));
   ASSERT_TRUE(parser.Next());
   ASSERT_EQ(parser.statement(), Statement(SqliteSql{}));
   ASSERT_EQ(parser.statement_sql(), FindSubstr(res, "select 1"));
@@ -296,7 +351,9 @@
   ASSERT_TRUE(parser.Next());
   ASSERT_EQ(parser.statement(),
             Statement(CreateView{
+                false,
                 "foo",
+                SqlSource::FromExecuteQuery("SELECT 'a' as foo, 42 AS bar"),
                 SqlSource::FromExecuteQuery(
                     "CREATE VIEW foo AS SELECT 'a' as foo, 42 AS bar"),
                 {{"$foo", sql_argument::Type::kString},
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h b/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h
index 8382fe6..5a46950 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_test_utils.h
@@ -46,7 +46,8 @@
 
 inline bool operator==(const PerfettoSqlParser::CreateView& a,
                        const PerfettoSqlParser::CreateView& b) {
-  return std::tie(a.name, a.sql) == std::tie(b.name, b.sql);
+  return std::tie(a.name, a.create_view_sql) ==
+         std::tie(b.name, b.create_view_sql);
 }
 
 inline bool operator==(const PerfettoSqlParser::Include& a,
@@ -82,7 +83,8 @@
   }
   if (auto* tab = std::get_if<PerfettoSqlParser::CreateView>(&line)) {
     return stream << "CreateView(name=" << testing::PrintToString(tab->name)
-                  << ", sql=" << testing::PrintToString(tab->sql) << ")";
+                  << ", sql=" << testing::PrintToString(tab->create_view_sql)
+                  << ")";
   }
   if (auto* macro = std::get_if<PerfettoSqlParser::CreateMacro>(&line)) {
     return stream << "CreateTable(name=" << testing::PrintToString(macro->name)
diff --git a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
index d9dc54d..c78d7f9 100644
--- a/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/BUILD.gn
@@ -26,6 +26,7 @@
     "intervals",
     "linux",
     "pkvm",
+    "prelude",
     "sched",
   ]
   generated_header = "stdlib.h"
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql b/src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql
index 980003b..828400e 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/battery_stats.sql
@@ -132,10 +132,10 @@
 ) AS
 SELECT
   ts,
+  IFNULL(LEAD(ts) OVER (PARTITION BY name ORDER BY ts) - ts, -1) AS dur,
   name AS track_name,
   CAST(value AS INT64) AS value,
-  android_battery_stats_counter_to_string(name, value) AS value_name,
-  IFNULL(LEAD(ts) OVER (PARTITION BY name ORDER BY ts) - ts, -1) AS dur
+  android_battery_stats_counter_to_string(name, value) AS value_name
 FROM counter
 JOIN counter_track
   ON counter.track_id = counter_track.id
@@ -166,10 +166,10 @@
   dur INT,
   -- The name of the counter track.
   track_name STRING,
-  -- The counter value as a number.
-  value INT,
-  -- The counter value as a human-readable string.
-  value_name STRING
+  -- String value.
+  str_value STRING,
+  -- Int value.
+  int_value INT
 ) AS
 WITH
   event_markers AS (
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
index 7a5257b..8642fe2 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/binder.sql
@@ -183,14 +183,14 @@
   client_utid INT,
   -- Tid of the client thread.
   client_tid INT,
+  -- Pid of the client process.
+  client_pid INT,
+  -- Whether the txn was initiated from the main thread of the client process.
+  is_main_thread BOOL,
   -- timestamp of the client txn.
   client_ts INT,
   -- dur of the client txn.
   client_dur INT,
-  -- oom score of the client process at the start of the txn.
-  client_oom_score INT,
-  -- Whether the txn was initiated from the main thread of the client process.
-  is_main_thread BOOL,
   -- slice id of the binder reply.
   binder_reply_id INT,
   -- name of the server process.
@@ -203,10 +203,14 @@
   server_utid INT,
   -- Tid of the server thread.
   server_tid INT,
+  -- Pid of the server thread.
+  server_pid INT,
   -- timestamp of the server txn.
   server_ts INT,
   -- dur of the server txn.
   server_dur INT,
+  -- oom score of the client process at the start of the txn.
+  client_oom_score INT,
   -- oom score of the server process at the start of the reply.
   server_oom_score INT
 ) AS
@@ -249,8 +253,16 @@
 CREATE PERFETTO VIEW android_sync_binder_thread_state_by_txn(
   -- slice id of the binder txn
   binder_txn_id INT,
+  -- Client timestamp
+  client_ts INT,
+  -- Client tid
+  client_tid INT,
   -- slice id of the binder reply
   binder_reply_id INT,
+  -- Server timestamp
+  server_ts INT,
+  -- Server tid
+  server_tid INT,
   -- whether thread state is on the txn or reply side
   thread_state_type STRING,
   -- a thread_state that occurred in the txn
@@ -298,8 +310,16 @@
 CREATE PERFETTO VIEW android_sync_binder_blocked_functions_by_txn(
   -- slice id of the binder txn
   binder_txn_id INT,
+  -- Client ts
+  client_ts INT,
+  -- Client tid
+  client_tid INT,
   -- slice id of the binder reply
   binder_reply_id INT,
+  -- Server ts
+  server_ts INT,
+  -- Server tid
+  server_tid INT,
   -- whether thread state is on the txn or reply side
   thread_state_type STRING,
   -- blocked kernel function in a thread state
@@ -414,14 +434,14 @@
   client_utid INT,
   -- Tid of the client thread.
   client_tid INT,
+  -- Pid of the client thread.
+  client_pid INT,
+  -- Whether the txn was initiated from the main thread of the client process.
+  is_main_thread BOOL,
   -- timestamp of the client txn.
   client_ts INT,
   -- dur of the client txn.
   client_dur INT,
-  -- oom score of the client process at the start of the txn.
-  client_oom_score INT,
-  -- Whether the txn was initiated from the main thread of the client process.
-  is_main_thread BOOL,
   -- slice id of the binder reply.
   binder_reply_id INT,
   -- name of the server process.
@@ -434,10 +454,14 @@
   server_utid INT,
   -- Tid of the server thread.
   server_tid INT,
+  -- Pid of the server thread.
+  server_pid INT,
   -- timestamp of the server txn.
   server_ts INT,
   -- dur of the server txn.
   server_dur INT,
+  -- oom score of the client process at the start of the txn.
+  client_oom_score INT,
   -- oom score of the server process at the start of the reply.
   server_oom_score INT
 ) AS
@@ -469,14 +493,14 @@
   client_utid INT,
   -- Tid of the client thread.
   client_tid INT,
+  -- Pid of the client thread.
+  client_pid INT,
+  -- Whether the txn was initiated from the main thread of the client process.
+  is_main_thread BOOL,
   -- timestamp of the client txn.
   client_ts INT,
   -- dur of the client txn.
   client_dur INT,
-  -- oom score of the client process at the start of the txn.
-  client_oom_score INT,
-  -- Whether the txn was initiated from the main thread of the client process.
-  is_main_thread BOOL,
   -- slice id of the binder reply.
   binder_reply_id INT,
   -- name of the server process.
@@ -489,10 +513,14 @@
   server_utid INT,
   -- Tid of the server thread.
   server_tid INT,
+  -- Pid of the server thread.
+  server_pid INT,
   -- timestamp of the server txn.
   server_ts INT,
   -- dur of the server txn.
   server_dur INT,
+  -- oom score of the client process at the start of the txn.
+  client_oom_score INT,
   -- oom score of the server process at the start of the reply.
   server_oom_score INT,
   -- whether the txn is synchronous or async (oneway).
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql b/src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql
index d7152b3..22fe345 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/dvfs.sql
@@ -90,16 +90,14 @@
 
 -- Aggregates dvfs counter slice for residency
 CREATE PERFETTO VIEW android_dvfs_counter_residency(
-  -- Id of the corresponding slice in slices table.
-  slice_id INT,
-  -- CPU that entered hypervisor.
-  cpu INT,
-  -- Timestamp when CPU entered hypervisor (in nanoseconds).
-  ts INT,
-  -- How much time CPU spent in hypervisor (in nanoseconds).
+  -- Counter name.
+  name STRING,
+  -- Counter value.
+  value INT,
+  -- Counter duration.
   dur INT,
-  -- Reason for entering hypervisor (e.g. host_hcall, host_mem_abort), or NULL if unknown.
-  reason STRING
+  -- Counter duration as a percentage of total duration.
+  pct FLOAT
 ) AS
 WITH
 total AS (
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/io.sql b/src/trace_processor/perfetto_sql/stdlib/android/io.sql
index d4500fd..b5d7021 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/io.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/io.sql
@@ -16,19 +16,19 @@
 -- Aggregates f2fs IO and latency stats by counter name.
 CREATE PERFETTO VIEW android_io_f2fs_counter_stats(
   -- Counter name on which all the other values are aggregated on.
-  counter_name STRING,
+  name STRING,
   -- Sum of all counter values for the counter name.
-  counter_sum INT,
+  sum INT,
   -- Max of all counter values for the counter name.
-  counter_max INT,
+  max INT,
   -- Min of all counter values for the counter name.
-  counter_min INT,
+  min INT,
   -- Duration between the first and last counter value for the counter name.
-  counter_dur INT,
+  dur INT,
   -- Count of all the counter values for the counter name.
-  counter_count INT,
+  count INT,
   -- Avergate of all the counter values for the counter name.
-  counter_avg DOUBLE
+  avg DOUBLE
 ) AS
 SELECT
   STR_SPLIT(counter_track.name, '].', 1) AS name,
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql b/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql
index 47cc758..6ed7232 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/process_metadata.sql
@@ -26,6 +26,10 @@
   upid INT,
   -- Process name.
   process_name STRING,
+  -- Android app UID.
+  uid INT,
+  -- Whether the UID is shared by multiple packages.
+  shared_uid BOOL,
   -- Name of the packages running in this process.
   package_name STRING,
   -- Package version code.
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn
index ee08f69..68c9bea 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/BUILD.gn
@@ -16,6 +16,7 @@
 
 perfetto_sql_source_set("startup") {
   sources = [
+    "internal_startup_events.sql",
     "internal_startups_maxsdk28.sql",
     "internal_startups_minsdk29.sql",
     "internal_startups_minsdk33.sql",
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startup_events.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startup_events.sql
new file mode 100644
index 0000000..ed2e44d
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startup_events.sql
@@ -0,0 +1,28 @@
+--
+-- Copyright 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
+--
+--     https://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.
+
+-- All activity startup events.
+CREATE PERFETTO TABLE internal_startup_events AS
+SELECT
+  ts,
+  dur,
+  ts + dur AS ts_end,
+  STR_SPLIT(s.name, ": ", 1) AS package_name
+FROM slice s
+JOIN process_track t ON s.track_id = t.id
+JOIN process USING(upid)
+WHERE
+  s.name GLOB 'launching: *'
+  AND (process.name IS NULL OR process.name = 'system_server');
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_maxsdk28.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_maxsdk28.sql
index 2ca73f0..fa77966 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_maxsdk28.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_maxsdk28.sql
@@ -13,10 +13,11 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+INCLUDE PERFETTO MODULE android.startup.internal_startup_events;
 
-INSERT INTO internal_all_startups
+CREATE PERFETTO TABLE internal_startups_maxsdk28 AS
 SELECT
-  "maxsdk28",
+  "maxsdk28" as sdk,
   ROW_NUMBER() OVER(ORDER BY ts) AS startup_id,
   le.ts,
   le.ts_end AS ts_end,
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk29.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk29.sql
index 37611df..e9536d1 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk29.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk29.sql
@@ -13,6 +13,8 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+INCLUDE PERFETTO MODULE android.startup.internal_startup_events;
+
 -- Marks the beginning of the trace and is equivalent to when the statsd startup
 -- logging begins.
 CREATE PERFETTO VIEW internal_activity_intent_received AS
@@ -48,9 +50,9 @@
 
 -- Use the starting event package name. The finish event package name
 -- is not reliable in the case of failed startups.
-INSERT INTO internal_all_startups
+CREATE PERFETTO TABLE internal_startups_minsdk29 AS
 SELECT
-  "minsdk29",
+  "minsdk29" as sdk,
   lpart.startup_id,
   lpart.ts,
   le.ts_end,
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk33.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk33.sql
index 9bb1937..118dda6 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk33.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/internal_startups_minsdk33.sql
@@ -13,12 +13,13 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+INCLUDE PERFETTO MODULE android.startup.internal_startup_events;
 
 CREATE PERFETTO VIEW internal_startup_async_events AS
 SELECT
   ts,
   dur,
-  SUBSTR(name, 19) AS startup_id
+  CAST(SUBSTR(name, 19) AS INT) AS startup_id
 FROM slice
 WHERE
   name GLOB 'launchingActivity#*'
@@ -27,7 +28,7 @@
 
 CREATE PERFETTO VIEW internal_startup_complete_events AS
 SELECT
-  STR_SPLIT(completed, ':', 0) AS startup_id,
+  CAST(STR_SPLIT(completed, ':', 0) AS INT) AS startup_id,
   STR_SPLIT(completed, ':', 2) AS package_name,
   CASE
     WHEN STR_SPLIT(completed, ':', 1) = 'completed-hot' THEN 'hot'
@@ -47,14 +48,14 @@
 )
 GROUP BY 1, 2, 3;
 
-INSERT INTO internal_all_startups
+CREATE PERFETTO TABLE internal_startups_minsdk33 AS
 SELECT
-  "minsdk33",
+  "minsdk33" as sdk,
   startup_id,
   ts,
   ts + dur AS ts_end,
   dur,
-  package_name,
+  package_name AS package,
   startup_type
 FROM internal_startup_async_events
 JOIN internal_startup_complete_events USING (startup_id);
diff --git a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
index 97959d7..fa55876 100644
--- a/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/android/startup/startups.sql
@@ -15,36 +15,18 @@
 
 INCLUDE PERFETTO MODULE common.slices;
 INCLUDE PERFETTO MODULE android.process_metadata;
-
--- All activity startup events.
-CREATE PERFETTO TABLE internal_startup_events AS
-SELECT
-  ts,
-  dur,
-  ts + dur AS ts_end,
-  STR_SPLIT(s.name, ": ", 1) AS package_name
-FROM slice s
-JOIN process_track t ON s.track_id = t.id
-JOIN process USING(upid)
-WHERE
-  s.name GLOB 'launching: *'
-  AND (process.name IS NULL OR process.name = 'system_server');
-
--- Gather all startup data. Populate by different sdks.
-CREATE TABLE internal_all_startups(
-  sdk STRING,
-  startup_id INTEGER BIGINT,
-  ts BIGINT,
-  ts_end BIGINT,
-  dur BIGINT,
-  package STRING,
-  startup_type STRING
-);
-
 INCLUDE PERFETTO MODULE android.startup.internal_startups_maxsdk28;
 INCLUDE PERFETTO MODULE android.startup.internal_startups_minsdk29;
 INCLUDE PERFETTO MODULE android.startup.internal_startups_minsdk33;
 
+-- Gather all startup data. Populate by different sdks.
+CREATE PERFETTO TABLE internal_all_startups AS
+SELECT sdk, startup_id, ts, ts_end, dur, package, startup_type FROM internal_startups_maxsdk28
+UNION ALL
+SELECT sdk, startup_id, ts, ts_end, dur, package, startup_type FROM internal_startups_minsdk29
+UNION ALL
+SELECT sdk, startup_id, ts, ts_end, dur, package, startup_type FROM internal_startups_minsdk33;
+
 -- All activity startups in the trace by startup id.
 -- Populated by different scripts depending on the platform version/contents.
 CREATE PERFETTO TABLE android_startups(
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn
index 7665102..adbd429 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/BUILD.gn
@@ -1,34 +1,14 @@
-# Copyright (C) 2022 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.
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
+# List the SQL files in the perfetto_sql_files.gni so the list can be
+# used in Chromium targets as well.
+import("perfetto_sql_files.gni")
+
+# This file is rolled to Perfetto so this relative path is intended to
+# work in the Perfetto repository but does not make sense here.
 import("../../../../../gn/perfetto_sql.gni")
-
 perfetto_sql_source_set("chrome_sql") {
-  sources = [
-    "chrome_scrolls.sql",
-    "cpu_powerups.sql",
-    "histograms.sql",
-    "interactions.sql",
-    "metadata.sql",
-    "page_loads.sql",
-    "scroll_jank/scroll_jank_intervals.sql",
-    "scroll_jank/scroll_jank_v3.sql",
-    "scroll_jank/scroll_jank_v3_cause.sql",
-    "scroll_jank/scroll_offsets.sql",
-    "scroll_jank/utils.sql",
-    "speedometer.sql",
-    "tasks.sql",
-    "vsync_intervals.sql",
-  ]
+  sources = chrome_stdlib_sql_files
 }
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql
index b03751d..f6e29f9 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/chrome_scrolls.sql
@@ -1,16 +1,6 @@
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 -- Defines slices for all of the individual scrolls in a trace based on the
 -- LatencyInfo-based scroll definition.
@@ -81,6 +71,8 @@
 CREATE PERFETTO VIEW chrome_scrolling_intervals(
   -- The unique identifier of the scroll interval. This may span multiple scrolls if they overlap.
   id INT,
+  -- Comma-separated list of scroll ids that are included in this interval.
+  scroll_ids STRING,
   -- The start timestamp of the scroll interval.
   ts INT,
   -- The duration of the scroll interval.
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql
index a890e70..3d51765 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/cpu_powerups.sql
@@ -1,16 +1,6 @@
--- Copyright 2022 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 -- Find causes for CPUs powering up.
 --
@@ -119,22 +109,24 @@
   -- Unique id for the thread that ran within the slice.
   utid INT,
   -- The CPU's power state before this slice.
-  previous_power_state INT
+  previous_power_state INT,
+  -- A unique ID for the CPU power-up.
+  powerup_id INT
 ) AS
-  SELECT
-    ts,
-    dur,
-    cpu,
-    id AS sched_id,
-    utid,
-    previous_power_state,
-    powerup_id
-  FROM internal_cpu_power_and_sched_slice
-  WHERE power_state = 0     -- Power-ups only.
-  GROUP BY cpu, powerup_id
-  HAVING ts = MIN(ts)       -- There will only be one MIN sched slice
-                            -- per CPU power up.
-  ORDER BY ts ASC;
+SELECT
+  ts,
+  dur,
+  cpu,
+  id AS sched_id,
+  utid,
+  previous_power_state,
+  powerup_id
+FROM internal_cpu_power_and_sched_slice
+WHERE power_state = 0     -- Power-ups only.
+GROUP BY cpu, powerup_id
+HAVING ts = MIN(ts)       -- There will only be one MIN sched slice
+                          -- per CPU power up.
+ORDER BY ts ASC;
 
 -- A view joining thread tracks and top-level slices.
 --
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency_description.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency_description.sql
new file mode 100644
index 0000000..ca89dae
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/event_latency_description.sql
@@ -0,0 +1,183 @@
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
+
+-- Source of truth of the descriptions of EventLatency stages.
+CREATE PERFETTO TABLE chrome_event_latency_stage_descriptions (
+    -- The name of the EventLatency stage.
+    name STRING,
+    -- A description of the EventLatency stage.
+    description STRING
+) AS
+WITH event_latency_descriptions(
+  name,
+  description)
+AS (
+VALUES
+  ('TouchRendererHandlingToBrowserMain',
+    'Interval between when the website handled blocking touch move to when ' ||
+    'the browser UI thread started processing the input. Blocking touch ' ||
+    'move happens when a touch event has to be handled by the website ' ||
+    'before being converted to a scroll.'),
+  ('GenerationToBrowserMain',
+    'Interval between OS-provided hardware input timestamp to when the ' ||
+    'browser UI thread began processing the input.'),
+  ('GenerationToRendererCompositor',
+    'Interval between OS-provided hardware input timestamp to when the ' ||
+    'renderer compositor thread starts handling the artificial TOUCH_PRESS ' ||
+    'browser injects in the kTouchScrollStarted event. See ' ||
+    'PrependTouchScrollNotification for more info.'),
+  ('BrowserMainToRendererCompositor',
+    'Interval between when Browser UI thread starts to process the input to ' ||
+    'renderer compositor thread starting to process it. This stage includes ' ||
+    'browser UI thread processing, and task queueing times on the IO and ' ||
+    'renderer compositor threads.'),
+  ('RendererCompositorQueueingDelay',
+    'Interval between when the input event is queued in the renderer ' ||
+    'compositor and start of the BeginImplFrame producing a frame ' ||
+    'containing this input.'),
+  ('RendererCompositorToMain',
+    'Interval between when the Renderer Compositor finishes processing the ' ||
+    'event and when the Renderer Main (CrRendererMain) starts processing ' ||
+    'the event, only seen when the compositor thread cannot handle the ' ||
+    'scroll event by itself (known as "slow path"), usually caused by the ' ||
+    'presence of blocking JS event listeners or complex page layout.'),
+  ('RendererCompositorProcessing',
+    'Interval corresponding to the Renderer Compositor thread processing ' ||
+    'the frame updates.'),
+  ('RendererMainProcessing',
+    'Interval corresponding to the Renderer Main thread processing the ' ||
+    'frame updates.'),
+  ('EndActivateToSubmitCompositorFrame',
+    'Interval that the Renderer Compositor waits for the GPU to flush a ' ||
+    'frame to submit a new one.'),
+  ('SubmitCompositorFrameToPresentationCompositorFrame',
+    'Interval between the first Renderer Frame received to when the system ' ||
+    'presented the fully composited frame on the screen. Note that on some ' ||
+    'systems/apps this is incomplete/inaccurate due to lack of feedback ' ||
+    'timestamps from the platform (Mac, iOS, Android Webview, etc).'),
+  ('ArrivedInRendererCompositorToTermination',
+    'Interval between when Renderer Compositor received the frame to when ' ||
+    'this input was decided to either be ignored or merged into another ' ||
+    'frame being produced. This could be a dropped frame, or just a normal ' ||
+    'coalescing.'),
+  ('RendererCompositorStartedToTermination',
+    'Interval between when Renderer Compositor started processing the frame ' ||
+    'to when this input was decided to either be ignored or merged into ' ||
+    'another frame being produced. This could be a dropped frame, or just a ' ||
+    'normal coalescing.'),
+  ('RendererMainFinishedToTermination',
+    'Interval between when Renderer Main finished processing the frame ' ||
+    'to when this input was decided to either be ignored or merged into ' ||
+    'another frame being produced. This could be a dropped frame, or just a ' ||
+    'normal coalescing.'),
+  ('RendererCompositorFinishedToTermination',
+    'Interval between when Renderer Compositor finished processing the ' ||
+    'frame to when this input was decided to either be ignored or merged ' ||
+    'into another frame being produced. This could be just a normal ' ||
+    'coalescing.'),
+  ('RendererMainStartedToTermination',
+    'Interval between when Renderer Main started processing the frame ' ||
+    'to when this input was decided to either be ignored or merged into ' ||
+    'another frame being produced. This could be a dropped frame, or just a ' ||
+    'normal coalescing.'),
+  ('RendererCompositorFinishedToBeginImplFrame',
+    'Interval when Renderer Compositor has finished processing a vsync ' ||
+    '(with input), but did not end up producing a CompositorFrame due to ' ||
+    'reasons such as waiting on main thread, and is now waiting for the ' ||
+    'next BeginFrame from the GPU VizCompositor.'),
+  ('RendererCompositorFinishedToCommit',
+    'Interval between when the Renderer Compositor has finished its work ' ||
+    'and the current tree state will be committed from the Renderer Main ' ||
+    '(CrRendererMain) thread.'),
+  ('RendererCompositorFinishedToEndCommit',
+    'Interval between when the Renderer Compositor finishing processing to ' ||
+    'the Renderer Main (CrRendererMain) both starting and finishing the ' ||
+    'commit.'),
+  ('RendererCompositorFinishedToActivation',
+    'Interval of activation without a previous commit (not as a stage with ' ||
+    'ToEndCommit). Activation occurs on the Renderer Compositor Thread ' ||
+    'after it has been notified of a fully committed RendererMain tree.'),
+  ('RendererCompositorFinishedToEndActivate',
+    'Interval when the Renderer Compositor has finished processing and ' ||
+    'activating the Tree.'),
+  ('RendererCompositorFinishedToSubmitCompositorFrame',
+    'Interval when processing does not need to wait for a commit (can do an ' ||
+    'early out) for activation and can go straight to providing the frame ' ||
+    'to the GPU VizCompositor. The Renderer Compositor is waiting for the ' ||
+    'GPU to flush a frame so that it can then submit a new frame.'),
+  ('RendererMainFinishedToBeginImplFrame',
+    'Interval when the input was sent first to the RendererMain thread and ' ||
+    'now requires the Renderer Compositor to react, aka it is is waiting ' ||
+    'for a BeginFrame signal.'),
+  ('RendererMainFinishedToSendBeginMainFrame',
+    'Interval during which the Renderer Main (CrRendererMain) thread is ' ||
+    'waiting for BeginMainFrame.'),
+  ('RendererMainFinishedToCommit',
+    'Interval when the Renderer Main (CrRendererMain) is ready to commit ' ||
+    'its work to the Renderer Compositor.'),
+  ('BeginImplFrameToSendBeginMainFrame',
+    'Interval during which the Renderer Compositor has received the ' ||
+    'BeginFrame signal from the GPU VizCompositor, and now needs to send it ' ||
+    'to the Renderer Main thread (CrRendererMain).'),
+  ('RendererCompositorFinishedToSendBeginMainFrame',
+    'Interval during which the Renderer Compositor is waiting for a ' ||
+    'BeginFrame from the GPU VizCompositor, and it expects to have to do ' ||
+    'work on the Renderer Main thread (CrRendererMain), so we are waiting ' ||
+    'for a BeginMainFrame'),
+  ('SendBeginMainFrameToCommit',
+    'Interval when updates (such as HandleInputEvents, Animate, StyleUpdate ' ||
+    'and LayoutUpdate) are updatedon the Renderer Main thread ' ||
+    '(CrRendererMain).'),
+  ('Commit',
+    'Interval during which the Renderer Main thread (CrRendererMain) ' ||
+    'commits updates back to Renderer Compositor for activation. ' ||
+    'Specifically, the main thread copies its own version of layer tree ' ||
+    'onto the pending tree on the compositor thread. The main thread is ' ||
+    'blocked during the copying process.'),
+  ('EndCommitToActivation',
+    'Interval when the commit is ready and waiting for activation.'),
+  ('Activation',
+    'Interval when the layer trees and properties are on the pending tree ' ||
+    'is pused to the active tree on the Renderer Compositor.'),
+  ('SubmitToReceiveCompositorFrame',
+    'Interval of the delay b/w Renderer Compositor thread sending ' ||
+    'CompositorFrame and then GPU VizCompositorThread receiving the ' ||
+    'CompositorFrame.'),
+  ('ReceiveCompositorFrameToStartDraw',
+    'Interval between the first frame received to when all frames (or ' ||
+    'timeouts have occured) and we start drawing. It can be blocked by ' ||
+    'other processes (e.g to draw a toolbar it waiting for information from ' ||
+    'the Browser) as it waits for timeouts or frames to be provided. This ' ||
+    'is the tree of dependencies that the GPU VizCompositor is waiting for ' ||
+    'things to arrive. That is creating a single frame for multiple ' ||
+    'compositor frames. '),
+  ('StartDrawToSwapStart',
+    'Interval when all compositing sources are done, or compositing ' ||
+    'deadline passes - the viz thread takes all the latest composited ' ||
+    'surfaces and issues the software draw instructions to layer the ' ||
+    'composited tiles, this substage ends when the swap starts on Gpu ' ||
+    'CompositorGpuThread.'),
+  ('SwapStartToBufferAvailable',
+    'Interval that is a substage of stage "Swap" when the framebuffer ' ||
+    'is prepared by the system and the fence Chrome waits on before ' ||
+    'writing is signalled, and Chrome can start transferring the new frame.'),
+  ('BufferAvailableToBufferReady',
+    'Interval that is a Ssubstage of stage "Swap" when Chrome is ' ||
+    'transferring a new frame to when it has finished completely sending a ' ||
+    'frame to the framebuffer.'),
+  ('BufferReadyToLatch',
+    'Interval that is a substage of stage "Swap", when the system latches ' ||
+    'and is ready to use the frame, and then it can get to work producing ' ||
+    'the final frame.'),
+  ('LatchToSwapEnd',
+    'Intereval that is a substage of stage "Swap", when the latch has ' ||
+    'finished until the frame is fully swapped and in the queue of frames ' ||
+    'to be presented.'),
+  ('SwapEndToPresentationCompositorFrame',
+    'Interval that the frame is presented on the screen (and pixels became ' ||
+    'visible).'))
+SELECT
+  name,
+  description
+FROM event_latency_descriptions;
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/histograms.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/histograms.sql
index e88f76a..b7bb525 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/histograms.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/histograms.sql
@@ -1,16 +1,6 @@
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 DROP VIEW IF EXISTS chrome_histograms;
 
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql
index e8764a6..129258c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/interactions.sql
@@ -1,16 +1,6 @@
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 -- This file specifies common metrics/tables for critical user interactions. It
 -- is expected to be in flux as metrics are added across different CUI types.
@@ -38,7 +28,7 @@
   dur INT
 ) AS
 SELECT
-  navigation_id AS scoped_id,
+  id AS scoped_id,
   'chrome_page_loads' AS type,
   'PageLoad' AS name,
   navigation_start_ts AS ts,
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql
index 8905f5b..1b31c0c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/metadata.sql
@@ -1,16 +1,6 @@
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 -- Returns hardware class of the device, often use to find device brand
 -- and model.
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql
index 97afa3f..73e44e9 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/page_loads.sql
@@ -1,16 +1,6 @@
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 -- TODO(b/306300843): The recorded navigation ids are not guaranteed to be
 -- unique within a trace; they are only guaranteed to be unique within a single
@@ -33,18 +23,23 @@
 RETURNS TABLE(
   ts LONG,
   dur LONG,
-  navigation_id INT
+  navigation_id INT,
+  browser_upid INT
 ) AS
 SELECT
   ts,
   dur,
   EXTRACT_ARG(arg_set_id, 'page_load.navigation_id')
-    AS navigation_id
-FROM slice
+    AS navigation_id,
+  upid AS browser_upid
+FROM process_slice
 WHERE name = $event_name;
 
 -- Chrome page loads, including associated high-level metrics and properties.
 CREATE PERFETTO TABLE chrome_page_loads(
+  -- ID of the navigation and Chrome browser process; this combination is
+  -- unique to every individual navigation.
+  id INT,
   -- ID of the navigation associated with the page load (i.e. the cross-document
   -- navigation in primary main frame which created this page's main document).
   -- Also note that navigation_id is specific to a given Chrome browser process,
@@ -83,12 +78,13 @@
   browser_upid INT
 ) AS
 SELECT
+  ROW_NUMBER() OVER(ORDER BY fcp.ts) AS id,
   fcp.navigation_id,
   fcp.ts AS navigation_start_ts,
   fcp.dur AS fcp,
   fcp.ts + fcp.dur AS fcp_ts,
   lcp.dur AS lcp,
-  IFNULL(lcp.dur, 0) + IFNULL(lcp.ts, 0) AS lcp_ts,
+  lcp.dur + lcp.ts AS lcp_ts,
   load_fired.ts AS dom_content_loaded_event_ts,
   start_load.ts AS load_event_ts,
   timing_loaded.ts AS mark_fully_loaded_ts,
@@ -99,19 +95,19 @@
 FROM internal_fcp_metrics fcp
 LEFT JOIN
   internal_page_load_metrics('PageLoadMetrics.NavigationToLargestContentfulPaint') lcp
-    USING (navigation_id)
+    USING (navigation_id, browser_upid)
 LEFT JOIN
   internal_page_load_metrics('PageLoadMetrics.NavigationToDOMContentLoadedEventFired') load_fired
-    using (navigation_id)
+    USING (navigation_id, browser_upid)
 LEFT JOIN
   internal_page_load_metrics('PageLoadMetrics.NavigationToMainFrameOnLoad') start_load
-    using (navigation_id)
+    USING (navigation_id, browser_upid)
 LEFT JOIN
   internal_page_load_metrics('PageLoadMetrics.UserTimingMarkFullyLoaded') timing_loaded
-    using (navigation_id)
+    USING (navigation_id, browser_upid)
 LEFT JOIN
   internal_page_load_metrics('PageLoadMetrics.UserTimingMarkFullyVisible') timing_visible
-    using (navigation_id)
+    USING (navigation_id, browser_upid)
 LEFT JOIN
   internal_page_load_metrics('PageLoadMetrics.UserTimingMarkInteractive') timing_interactive
-    using (navigation_id);
+    USING (navigation_id, browser_upid);
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni b/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni
new file mode 100644
index 0000000..5edf42c
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/perfetto_sql_files.gni
@@ -0,0 +1,24 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# List of files in //base/tracing/stdlib/chrome and subdirectories.
+chrome_stdlib_sql_files = [
+  "chrome_scrolls.sql",
+  "cpu_powerups.sql",
+  "event_latency_description.sql",
+  "histograms.sql",
+  "interactions.sql",
+  "metadata.sql",
+  "page_loads.sql",
+  "speedometer.sql",
+  "tasks.sql",
+  "vsync_intervals.sql",
+  "scroll_jank/scroll_jank_cause_map.sql",
+  "scroll_jank/scroll_jank_cause_utils.sql",
+  "scroll_jank/scroll_jank_intervals.sql",
+  "scroll_jank/scroll_jank_v3_cause.sql",
+  "scroll_jank/scroll_jank_v3.sql",
+  "scroll_jank/scroll_offsets.sql",
+  "scroll_jank/utils.sql",
+]
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_map.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_map.sql
new file mode 100644
index 0000000..f049075
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_map.sql
@@ -0,0 +1,94 @@
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
+
+-- Source of truth of the descriptions of EventLatency-based scroll jank causes.
+CREATE PERFETTO TABLE chrome_scroll_jank_cause_descriptions (
+  -- The name of the EventLatency stage.
+  event_latency_stage STRING,
+  -- The process where the cause of scroll jank occurred.
+  cause_process STRING,
+  -- The thread where the cause of scroll jank occurred.
+  cause_thread STRING,
+  -- A description of the cause of scroll jank.
+  cause_description STRING
+) AS
+WITH cause_descriptions(
+  event_latency_stage,
+  cause_process,
+  cause_thread,
+  cause_description)
+AS (
+VALUES
+  ('GenerationToBrowserMain', 'Browser', 'CrBrowserMain',
+    'This also corresponds to a matching InputLatency::TouchMove. Key ' ||
+    'things to look for: Browser Main thread (CrBrowserMain) is busy, often ' ||
+    'running tasks. The true cause can be confirmed by checking which tasks ' ||
+    'are being run on CrBrowserMain, or checking any ScopedBlockingCall ' ||
+    'slices during this stage from a ThreadPoolForegroundWorker, or ' ||
+    'checking if the NetworkService is busy. Common causes may include page' ||
+    'navigations (same document and new pages), slow BeginMainFrames, and ' ||
+    'Java Choreographer slowdowns.'),
+  ('RendererCompositorQueueingDelay', 'Renderer', 'Compositor',
+    'The renderer needs to decide to produce a frame in response to a ' ||
+    'BeginFrame signal. Sometimes it can not because it is waiting on the ' ||
+    'RendererMain thread to do touch targeting or javascript handling or ' ||
+    'other such things causing a long queuing delay after it has already ' ||
+    'started the scroll (so the TouchStart has been processed).'),
+  ('RendererCompositorQueueingDelay', 'GPU', 'VizCompositorThread',
+    'Waiting for a BeginFrame to be sent. Key things to look for: check if ' ||
+    'a fling occurred before or during the scroll; flings produce a single ' ||
+    'input and result in multiple inputs coalescing into a single frame.'),
+  ('ReceiveCompositorFrameToStartDraw', 'GPU', 'VizCompositorThread',
+    'A delay when the VizCompositor is waiting for the frame, but may be ' ||
+    'connected to other processes and threads. Key things to look for: ' ||
+    'check the BeginFrame task that finished during this EventLatency. The ' ||
+    'VizCompositor holds onto the frame/does not send it on. Alternately ' ||
+    'the system may be holding on to the buffer.'),
+  ('ReceiveCompositorFrameToStartDraw', 'GPU', 'CrGpuMain',
+    'Key things to look for: if the GPU Main thread is busy, and does not ' ||
+    'release the buffer; specific causes will be on the GPU Main thread. If ' ||
+    'this thread is not busy, the buffer may be held by the system instead.'),
+  ('ReceiveCompositorFrameToStartDraw', 'Browser', 'CrBrowserMain',
+    'Key things to look for: the toolbar on the Browser may be blocked by ' ||
+    'other tasks.'),
+  ('BufferReadyToLatch', 'GPU', 'VizCompositorThread',
+    'Often a scheduling issue. The frame was submitted, but missed the ' ||
+    'latch in the system that was received from the previous frame. The ' ||
+    'system only latches a buffer once per frame; when the latch deadline ' ||
+    'is missed, the system is forced to wait for another vsync interval to ' ||
+    'latch again. Key things to look for: whether the event duration before ' ||
+    'BufferReadyToLatch stage of the previous EventLatency is longer or ' ||
+    'shorter than the event duration before BufferReadyToLatch in the ' ||
+    'current EventLatency. If this duration is longer, then this is a ' ||
+    'System problem. If this duration is shorter, then it is a Chrome ' ||
+    'problem. The previous frame may have been drawn too quickly, or the ' ||
+    'GPU may be delayed.'),
+  ('SwapEndToPresentationCompositorFrame', 'GPU', 'VizCompositorThread',
+    'May be attributed to a scheduling issue as with BufferReadyToLatch. ' ||
+    'The frame was submitted, but missed the latch in the system that was ' ||
+    'received from the previous frame. The system only latches a buffer ' ||
+    'once per frame; when the latch deadline is missed, the system is ' ||
+    'forced to wait for another vsync interval to latch again. Key things ' ||
+    'to look for: whether the event duration before BufferReadyToLatch ' ||
+    'stage of the previous EventLatency is longer or shorter than the event ' ||
+    'duration before BufferReadyToLatch in the current EventLatency. If ' ||
+    'this duration is longer, then this is a System problem. If this ' ||
+    'duration is shorter, then it is a Chrome problem. The previous frame ' ||
+    'may have been drawn too quickly, or the GPU may be delayed.'),
+  ('SwapEndToPresentationCompositorFrame', 'GPU', 'CrGpuMain',
+    'Key things to look for: whether StartDrawToBufferAvailable is also ' ||
+    'present during this EventLatency. If so, then the GPU main thread may ' ||
+    'be descheduled or busy. If surfaceflinger is available, check there as ' ||
+    'well.'),
+  ('SwapEndToPresentationCompositorFrame', 'GPU', 'surfaceflinger',
+    'Key things to look for: whether StartDrawToBufferAvailable is also ' ||
+    'present during this EventLatency. If so, then the VizCompositor has ' ||
+    'not received a signal from surfaceflinger to start writing into the ' ||
+    'buffer.'))
+SELECT
+  event_latency_stage,
+  cause_process,
+  cause_thread,
+  cause_description
+FROM cause_descriptions;
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_utils.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_utils.sql
new file mode 100644
index 0000000..2c6d0d0
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_cause_utils.sql
@@ -0,0 +1,48 @@
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
+
+
+-- Retrieve the thread id of the thread on a particular process, if the name of
+-- that process is known. Returns an error if there are multiple threads in
+-- the given process with the same name.
+CREATE PERFETTO FUNCTION internal_find_utid_by_upid_and_name(
+  -- Unique process id
+  upid INT,
+  -- The name of the thread
+  thread_name STRING)
+RETURNS TABLE (
+  -- Unique thread id.
+  utid INT
+) AS
+SELECT
+  DISTINCT utid
+FROM thread
+WHERE upid = $upid
+  AND name = $thread_name;
+
+-- Function to retrieve the track id of the thread on a particular process if
+-- there are any slices during a particular EventLatency slice duration; this
+-- upid/thread combination refers to a cause of Scroll Jank.
+CREATE PERFETTO FUNCTION chrome_select_scroll_jank_cause_track(
+  -- The slice id of an EventLatency slice.
+  event_latency_id INT,
+  -- The process id that the thread is on.
+  upid INT,
+  -- The name of the thread.
+  thread_name STRING)
+RETURNS TABLE (
+  -- The track id associated with |thread| on the process with |upid|.
+  track_id INT
+) AS
+SELECT
+ DISTINCT track_id
+FROM thread_slice
+WHERE utid IN
+  (
+    SELECT
+      utid
+    FROM internal_find_utid_by_upid_and_name($upid, $thread_name)
+  )
+  AND ts >= (SELECT ts FROM slice WHERE id = $event_latency_id LIMIT 1)
+  AND ts <= (SELECT ts + dur FROM slice WHERE id = $event_latency_id LIMIT 1);
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql
index 27186ab..d85d7c6 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_intervals.sql
@@ -1,16 +1,6 @@
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 INCLUDE PERFETTO MODULE chrome.chrome_scrolls;
 INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_v3;
@@ -87,10 +77,10 @@
 CREATE PERFETTO VIEW chrome_scroll_stats(
   -- Id of the individual scroll.
   scroll_id INT,
-  -- The number of missed vsyncs in the scroll.
-  missed_vsyncs INT,
   -- The number of frames in the scroll.
   frame_count INT,
+  -- The number of missed vsyncs in the scroll.
+  missed_vsyncs INT,
   -- The number presented frames in the scroll.
   presented_frame_count INT,
   -- The number of janky frames in the scroll.
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql
index d27c862..4b20b6b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3.sql
@@ -1,17 +1,6 @@
---
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 INCLUDE PERFETTO MODULE common.slices;
 
@@ -276,12 +265,14 @@
 CREATE PERFETTO VIEW chrome_frame_info_with_delay(
   -- gesture scroll slice id.
   id INT,
-  -- OS timestamp of the first touch move arrival within a frame.
-  min_start_ts INT,
   -- OS timestamp of the last touch move arrival within a frame.
   max_start_ts INT,
+  -- OS timestamp of the first touch move arrival within a frame.
+  min_start_ts INT,
   -- The scroll which the touch belongs to.
   scroll_id INT,
+  -- ID of the associated scroll update.
+  scroll_update_id INT,
   -- Trace ids of all frames presented in at this vsync.
   encapsulated_scroll_ids INT,
   -- Summation of all delta_y of all gesture scrolls in this frame.
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql
index f681381..d746457 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_jank_v3_cause.sql
@@ -1,17 +1,6 @@
---
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 -- Helper functions for scroll_jank_v3 metric computation.
 
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql
index bfa4580..41ead9a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/scroll_offsets.sql
@@ -1,16 +1,6 @@
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 -- This file creates two public views:
 --     - chrome_scroll_input_offsets and
@@ -107,11 +97,11 @@
 
 -- All of the presented frame scroll update ids.
 CREATE PERFETTO VIEW chrome_deltas_presented_frame_scroll_update_ids(
-  -- ID slice of the presented frame.
-  arg_set_id INT,
   -- A scroll update id that was included in the presented frame.
   -- There may be zero, one, or more.
-  scroll_update_id INT
+  scroll_update_id INT,
+  -- Slice id
+  id INT
 ) AS
 SELECT
   args.int_value AS scroll_update_id,
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
index 079443b..64c5e00 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/scroll_jank/utils.sql
@@ -1,17 +1,6 @@
---
--- Copyright 2022 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 --
 -- Those are helper functions used in computing jank metrics
 
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer.sql
index c2fc1c7..e29811a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/speedometer.sql
@@ -1,16 +1,6 @@
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 -- Annotates a trace with Speedometer 2.1 related information.
 --
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
index 054940f..0c7b18b 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql
@@ -1,18 +1,6 @@
---
--- Copyright 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
---
---     https://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.
---
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 INCLUDE PERFETTO MODULE common.slices;
 
@@ -268,13 +256,15 @@
   -- Whether this slice is a part of non-accelerated capture toolbar screenshot.
   is_software_screenshot BOOL,
   -- Whether this slice is a part of accelerated capture toolbar screenshot.
-  is_hardware_screenshot BOOL
+  is_hardware_screenshot BOOL,
+  -- Slice id.
+  slice_id INT
 ) AS
 SELECT
   java_view.name AS filtered_name,
   java_view.is_software_screenshot,
   java_view.is_hardware_screenshot,
-  slice.*
+  slice.id as slice_id
 FROM internal_chrome_java_views java_view
 JOIN slice USING (id);
 
@@ -373,6 +363,8 @@
 CREATE PERFETTO VIEW chrome_scheduler_tasks(
   -- Slice id.
   id INT,
+  -- Type.
+  type STRING,
   -- Name of the task.
   name STRING,
   -- Timestamp.
@@ -389,6 +381,8 @@
   process_name STRING,
   -- Same as slice.track_id.
   track_id INT,
+  -- Same as slice.category.
+  category STRING,
   -- Same as slice.depth.
   depth INT,
   -- Same as slice.parent_id.
diff --git a/src/trace_processor/perfetto_sql/stdlib/chrome/vsync_intervals.sql b/src/trace_processor/perfetto_sql/stdlib/chrome/vsync_intervals.sql
index 45cea5b..694cbc5 100644
--- a/src/trace_processor/perfetto_sql/stdlib/chrome/vsync_intervals.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/chrome/vsync_intervals.sql
@@ -1,16 +1,6 @@
--- Copyright 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
---
---     https://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.
+-- Copyright 2023 The Chromium Authors
+-- Use of this source code is governed by a BSD-style license that can be
+-- found in the LICENSE file.
 
 -- A simple table that checks the time between VSync (this can be used to
 -- determine if we're refreshing at 90 FPS or 60 FPS).
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/args.sql b/src/trace_processor/perfetto_sql/stdlib/common/args.sql
index 5fc4836..df0615a 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/args.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/args.sql
@@ -22,10 +22,10 @@
   -- Id of the arg set.
   arg_set_id INT,
   -- Key of the argument.
-  key STRING
+  arg_key STRING
 )
 -- Formatted value of the argument.
 RETURNS STRING AS
 SELECT display_value
 FROM args
-WHERE arg_set_id = $arg_set_id AND key = $key;
\ No newline at end of file
+WHERE arg_set_id = $arg_set_id AND key = $arg_key;
\ No newline at end of file
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql b/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
index 7c5cf9a..7ec8afe 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/percentiles.sql
@@ -16,12 +16,12 @@
 INCLUDE PERFETTO MODULE common.counters;
 INCLUDE PERFETTO MODULE common.timestamps;
 
-CREATE PERFETTO FUNCTION internal_number_generator(to INT)
+CREATE PERFETTO FUNCTION internal_number_generator(upper_limit INT)
 RETURNS TABLE(num INT) AS
 WITH nums AS
     (SELECT 1 num UNION SELECT num + 1
     from NUMS
-    WHERE num < $to)
+    WHERE num < $upper_limit)
 SELECT num FROM nums;
 
 --
diff --git a/src/trace_processor/perfetto_sql/stdlib/common/slices.sql b/src/trace_processor/perfetto_sql/stdlib/common/slices.sql
index e9c326d..36176c0 100644
--- a/src/trace_processor/perfetto_sql/stdlib/common/slices.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/common/slices.sql
@@ -57,6 +57,7 @@
 ) AS
 SELECT
   slice.id,
+  slice.type,
   slice.ts,
   slice.dur,
   slice.category,
diff --git a/src/trace_processor/perfetto_sql/stdlib/experimental/proto_path.sql b/src/trace_processor/perfetto_sql/stdlib/experimental/proto_path.sql
index 06be141..5ab538c 100644
--- a/src/trace_processor/perfetto_sql/stdlib/experimental/proto_path.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/experimental/proto_path.sql
@@ -15,8 +15,8 @@
 
 -- Creates a Stack consisting of one frame for a path in the
 -- EXPERIMENTAL_PROTO_PATH table.
-CREATE PERFETTO FUNCTON EXPERIMENTAL_PROTO_PATH_TO_FRAME(
-  -- Id of the path in EXPERIMENTAL_PROTO_PATH.
+CREATE PERFETTO FUNCTION experimental_proto_path_to_frame(
+-- Id of the path in EXPERIMENTAL_PROTO_PATH.
   path_id LONG)
 -- Stack with one frame
 RETURNS BYTES AS
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
new file mode 100644
index 0000000..19162d5
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/BUILD.gn
@@ -0,0 +1,19 @@
+# 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("../../../../../gn/perfetto_sql.gni")
+
+perfetto_sql_source_set("prelude") {
+  sources = [ "slices.sql" ]
+}
diff --git a/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql b/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql
new file mode 100644
index 0000000..a669f04
--- /dev/null
+++ b/src/trace_processor/perfetto_sql/stdlib/prelude/slices.sql
@@ -0,0 +1,32 @@
+--
+-- Copyright 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
+--
+--     https://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.
+
+-- Given two slice ids, returns whether the first is an ancestor of the second.
+CREATE PERFETTO FUNCTION slice_is_ancestor(
+  -- Id of the potential ancestor slice.
+  ancestor_id LONG,
+  -- Id of the potential descendant slice.
+  descendant_id LONG
+)
+-- Whether `ancestor_id` slice is an ancestor of `descendant_id`.
+RETURNS BOOL AS
+SELECT
+  ancestor.track_id = descendant.track_id AND
+  ancestor.ts <= descendant.ts AND
+  (ancestor.dur == -1 OR ancestor.ts + ancestor.dur >= descendant.ts + descendant.dur)
+FROM slice ancestor
+JOIN slice descendant
+WHERE ancestor.id = $ancestor_id
+  AND descendant.id = $descendant_id
\ No newline at end of file
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
index 23b064a..2a76c53 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -169,6 +169,17 @@
   return base::OkStatus();
 }
 
+base::Status SqliteEngine::UnregisterFunction(const char* name, int argc) {
+  int ret = sqlite3_create_function_v2(db_.get(), name, static_cast<int>(argc),
+                                       SQLITE_UTF8, nullptr, nullptr, nullptr,
+                                       nullptr, nullptr);
+  if (ret != SQLITE_OK) {
+    return base::ErrStatus("Unable to unregister function with name %s", name);
+  }
+  fn_ctx_.Erase({name, argc});
+  return base::OkStatus();
+}
+
 base::Status SqliteEngine::DeclareVirtualTable(const std::string& create_stmt) {
   int res = sqlite3_declare_vtab(db_.get(), create_stmt.c_str());
   if (res != SQLITE_OK) {
diff --git a/src/trace_processor/sqlite/sqlite_engine.h b/src/trace_processor/sqlite/sqlite_engine.h
index 1a1b089..42ef5ae 100644
--- a/src/trace_processor/sqlite/sqlite_engine.h
+++ b/src/trace_processor/sqlite/sqlite_engine.h
@@ -89,6 +89,9 @@
                                 FnCtxDestructor* ctx_destructor,
                                 bool deterministic);
 
+  // Unregisters a C++ function from SQL.
+  base::Status UnregisterFunction(const char* name, int argc);
+
   // Registers a SQLite virtual table module with the given name.
   template <typename Vtab, typename Context>
   void RegisterVirtualTableModule(const std::string& module_name,
diff --git a/src/trace_processor/tables/profiler_tables.py b/src/trace_processor/tables/profiler_tables.py
index 8361d8c..dfcc93e 100644
--- a/src/trace_processor/tables/profiler_tables.py
+++ b/src/trace_processor/tables/profiler_tables.py
@@ -161,7 +161,7 @@
         C('name', CppString()),
         C('mapping', CppTableId(STACK_PROFILE_MAPPING_TABLE)),
         C('rel_pc', CppInt64()),
-        C('symbol_set_id', CppOptional(CppUint32())),
+        C('symbol_set_id', CppOptional(CppUint32()), flags=ColumnFlag.DENSE),
         C('deobfuscated_name', CppOptional(CppString())),
     ],
     tabledoc=TableDoc(
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 6c3b209..a24ac52 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -752,6 +752,7 @@
   // Initalize the tables and views in the prelude.
   InitializePreludeTablesViews(engine_->sqlite_engine()->db());
 
+  // Register stdlib modules.
   auto stdlib_modules = GetStdlibModules();
   for (auto module_it = stdlib_modules.GetIterator(); module_it; ++module_it) {
     base::Status status =
@@ -920,6 +921,16 @@
                                   *metric.proto_field_name);
     }
   }
+
+  // Import prelude module.
+  {
+    auto result = engine_->Execute(SqlSource::FromTraceProcessorImplementation(
+        "INCLUDE PERFETTO MODULE prelude.*"));
+    if (!result.status().ok()) {
+      PERFETTO_FATAL("Failed to import prelude: %s",
+                     result.status().c_message());
+    }
+  }
 }
 
 namespace {
diff --git a/src/trace_processor/util/sql_modules.h b/src/trace_processor/util/sql_modules.h
index 4a52e7d..997acb0 100644
--- a/src/trace_processor/util/sql_modules.h
+++ b/src/trace_processor/util/sql_modules.h
@@ -22,6 +22,7 @@
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/basic_types.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index c02af04..a6bf239 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -220,14 +220,14 @@
     bool first_batch_in_cycle,
     CompactSchedBuffer* compact_sched_buf,
     const std::set<FtraceDataSource*>& started_data_sources) {
+  const auto sys_page_size = base::GetSysPageSize();
   size_t pages_read = 0;
   {
     metatrace::ScopedEvent evt(metatrace::TAG_FTRACE,
                                metatrace::FTRACE_CPU_READ_BATCH);
     for (; pages_read < max_pages;) {
-      uint8_t* curr_page = parsing_buf + (pages_read * base::kPageSize);
-      ssize_t res =
-          PERFETTO_EINTR(read(*trace_fd_, curr_page, base::kPageSize));
+      uint8_t* curr_page = parsing_buf + (pages_read * sys_page_size);
+      ssize_t res = PERFETTO_EINTR(read(*trace_fd_, curr_page, sys_page_size));
       if (res < 0) {
         // Expected errors:
         // EAGAIN: no data (since we're in non-blocking mode).
@@ -253,7 +253,7 @@
         PERFETTO_DLOG("[cpu%zu]: 0-sized read from ftrace pipe.", cpu_);
         break;
       }
-      PERFETTO_CHECK(res == static_cast<ssize_t>(base::kPageSize));
+      PERFETTO_CHECK(res == static_cast<ssize_t>(sys_page_size));
 
       pages_read += 1;
 
@@ -267,11 +267,11 @@
       // fragmentation, i.e. for the fact that the last trace event didn't fit
       // in the current page and hence the current page was terminated
       // prematurely.
-      static constexpr size_t kRoughlyAPage = base::kPageSize - 512;
+      static const size_t kRoughlyAPage = sys_page_size - 512;
       const uint8_t* scratch_ptr = curr_page;
       std::optional<PageHeader> hdr =
           ParsePageHeader(&scratch_ptr, table_->page_header_size_len());
-      PERFETTO_DCHECK(hdr && hdr->size > 0 && hdr->size <= base::kPageSize);
+      PERFETTO_DCHECK(hdr && hdr->size > 0 && hdr->size <= sys_page_size);
       if (!hdr.has_value()) {
         PERFETTO_ELOG("[cpu%zu]: can't parse page header", cpu_);
         break;
@@ -305,8 +305,8 @@
     if (pages_parsed_ok != pages_read) {
       const size_t first_bad_page_idx = pages_parsed_ok;
       const uint8_t* curr_page =
-          parsing_buf + (first_bad_page_idx * base::kPageSize);
-      LogInvalidPage(curr_page, base::kPageSize);
+          parsing_buf + (first_bad_page_idx * sys_page_size);
+      LogInvalidPage(curr_page, sys_page_size);
       PERFETTO_FATAL("Failed to parse ftrace page");
     }
   }
@@ -423,8 +423,9 @@
   size_t pages_parsed = 0;
   bool compact_sched_enabled = ds_config->compact_sched.enabled;
   for (; pages_parsed < pages_read; pages_parsed++) {
-    const uint8_t* curr_page = parsing_buf + (pages_parsed * base::kPageSize);
-    const uint8_t* curr_page_end = curr_page + base::kPageSize;
+    const uint8_t* curr_page =
+        parsing_buf + (pages_parsed * base::GetSysPageSize());
+    const uint8_t* curr_page_end = curr_page + base::GetSysPageSize();
     const uint8_t* parse_pos = curr_page;
     std::optional<PageHeader> page_header =
         ParsePageHeader(&parse_pos, table->page_header_size_len());
@@ -489,7 +490,7 @@
   // (clearing the bit internally).
   constexpr static uint64_t kMissedEventsFlag = (1ull << 31);
 
-  const uint8_t* end_of_page = *ptr + base::kPageSize;
+  const uint8_t* end_of_page = *ptr + base::GetSysPageSize();
   PageHeader page_header;
   if (!CpuReader::ReadAndAdvance<uint64_t>(ptr, end_of_page,
                                            &page_header.timestamp))
@@ -505,7 +506,7 @@
 
   page_header.size = size_and_flags & kDataSizeMask;
   page_header.lost_events = bool(size_and_flags & kMissedEventsFlag);
-  PERFETTO_DCHECK(page_header.size <= base::kPageSize);
+  PERFETTO_DCHECK(page_header.size <= base::GetSysPageSize());
 
   // Reject rest of the number, if applicable. On 32-bit, size_bytes - 4 will
   // evaluate to 0 and this will be a no-op. On 64-bit, this will advance by 4
diff --git a/src/traced/probes/ftrace/cpu_reader.h b/src/traced/probes/ftrace/cpu_reader.h
index 6fc629d..474c10a 100644
--- a/src/traced/probes/ftrace/cpu_reader.h
+++ b/src/traced/probes/ftrace/cpu_reader.h
@@ -64,7 +64,7 @@
     void AllocateIfNeeded() {
       // PagedMemory stays valid as long as it was allocated once.
       if (!ftrace_data_.IsValid()) {
-        ftrace_data_ = base::PagedMemory::Allocate(base::kPageSize *
+        ftrace_data_ = base::PagedMemory::Allocate(base::GetSysPageSize() *
                                                    kFtraceDataBufSizePages);
       }
       // Heap-allocated buffer gets freed and reallocated.
@@ -96,7 +96,7 @@
     }
     size_t ftrace_data_buf_pages() const {
       PERFETTO_DCHECK(ftrace_data_.size() ==
-                      base::kPageSize * kFtraceDataBufSizePages);
+                      base::GetSysPageSize() * kFtraceDataBufSizePages);
       return kFtraceDataBufSizePages;
     }
     CompactSchedBuffer* compact_sched_buf() const {
diff --git a/src/traced/probes/ftrace/cpu_reader_benchmark.cc b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
index c7db2a1..c9c9062 100644
--- a/src/traced/probes/ftrace/cpu_reader_benchmark.cc
+++ b/src/traced/probes/ftrace/cpu_reader_benchmark.cc
@@ -934,11 +934,12 @@
   ProtoTranslationTable* table = GetTable(test_case.name);
 
   auto repeated_pages =
-      std::make_unique<uint8_t[]>(base::kPageSize * page_repetition);
+      std::make_unique<uint8_t[]>(base::GetSysPageSize() * page_repetition);
   {
     auto page = PageFromXxd(test_case.data);
     for (size_t i = 0; i < page_repetition; i++) {
-      memcpy(&repeated_pages[i * base::kPageSize], &page[0], base::kPageSize);
+      memcpy(&repeated_pages[i * base::GetSysPageSize()], &page[0],
+             base::GetSysPageSize());
     }
   }
 
diff --git a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
index 002fd53..0559f02 100644
--- a/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
+++ b/src/traced/probes/ftrace/cpu_reader_fuzzer.cc
@@ -31,15 +31,9 @@
 #include "protos/perfetto/trace/ftrace/ftrace_event_bundle.pbzero.h"
 
 namespace perfetto {
-namespace {
-
-uint8_t g_page[base::kPageSize];
-
-}  // namespace
 
 using perfetto::protos::pbzero::FtraceEventBundle;
 
-void FuzzCpuReaderParsePage(const uint8_t* data, size_t size);
 void FuzzCpuReaderProcessPagesForDataSource(const uint8_t* data, size_t size);
 
 // TODO(rsavitski): make the fuzzer generate multi-page payloads.
@@ -50,8 +44,10 @@
         "Could not read table. "
         "This fuzzer must be run in the root directory.");
   }
-  memset(g_page, 0, base::kPageSize);
-  memcpy(g_page, data, std::min(base::kPageSize, size));
+
+  static uint8_t* g_page = new uint8_t[base::GetSysPageSize()];
+  memset(g_page, 0, base::GetSysPageSize());
+  memcpy(g_page, data, std::min(size_t(base::GetSysPageSize()), size));
 
   FtraceMetadata metadata{};
   FtraceDataSourceConfig ds_config{/*event_filter=*/EventFilter{},
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 1e69e3d..25fc580 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -164,7 +164,9 @@
 class BinaryWriter {
  public:
   BinaryWriter()
-      : size_(base::kPageSize), page_(new uint8_t[size_]), ptr_(page_.get()) {}
+      : size_(base::GetSysPageSize()),
+        page_(new uint8_t[size_]),
+        ptr_(page_.get()) {}
 
   template <typename T>
   void Write(T t) {
@@ -443,7 +445,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_EQ(44ul, page_header->size);
   EXPECT_FALSE(page_header->lost_events);
@@ -566,7 +568,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -609,7 +611,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -653,7 +655,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -745,7 +747,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -791,7 +793,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   ASSERT_FALSE(page_header->lost_events);
   ASSERT_LE(parse_pos + page_header->size, page_end);
@@ -867,10 +869,12 @@
   // Prepare a buffer with 8 contiguous pages, with the above contents.
   static constexpr size_t kTestPages = 8;
 
-  std::unique_ptr<uint8_t[]> buf(new uint8_t[base::kPageSize * kTestPages]());
+  std::unique_ptr<uint8_t[]> buf(
+      new uint8_t[base::GetSysPageSize() * kTestPages]());
   for (size_t i = 0; i < kTestPages; i++) {
-    void* dest = buf.get() + (i * base::kPageSize);
-    memcpy(dest, static_cast<const void*>(test_page_order[i]), base::kPageSize);
+    void* dest = buf.get() + (i * base::GetSysPageSize());
+    memcpy(dest, static_cast<const void*>(test_page_order[i]),
+           base::GetSysPageSize());
   }
   auto compact_sched_buf = std::make_unique<CompactSchedBuffer>();
 
@@ -988,7 +992,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -1037,7 +1041,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -1152,7 +1156,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -1351,7 +1355,7 @@
       InvalidCompactSchedEventFormatForTesting(), printk_formats);
   FtraceDataSourceConfig ds_config = EmptyConfig();
 
-  FakeEventProvider provider(base::kPageSize);
+  FakeEventProvider provider(base::GetSysPageSize());
 
   BinaryWriter writer;
 
@@ -1437,7 +1441,7 @@
   auto input = writer.GetCopy();
   auto length = writer.written();
 
-  BundleProvider bundle_provider(base::kPageSize);
+  BundleProvider bundle_provider(base::GetSysPageSize());
   FtraceMetadata metadata{};
 
   ASSERT_TRUE(CpuReader::ParseEvent(
@@ -1486,7 +1490,7 @@
 
   auto input = writer.GetCopy();
   auto length = writer.written();
-  BundleProvider bundle_provider(base::kPageSize);
+  BundleProvider bundle_provider(base::GetSysPageSize());
   FtraceMetadata metadata{};
 
   ASSERT_TRUE(CpuReader::ParseEvent(
@@ -1504,7 +1508,7 @@
 }
 
 TEST(CpuReaderTest, TaskRenameEvent) {
-  BundleProvider bundle_provider(base::kPageSize);
+  BundleProvider bundle_provider(base::GetSysPageSize());
 
   BinaryWriter writer;
   ProtoTranslationTable* table = GetTable("android_seed_N2F62_3.10.49");
@@ -1536,7 +1540,7 @@
 // zero-terminated strings in some cases. Even though it's a kernel bug, there's
 // no point in rejecting that.
 TEST(CpuReaderTest, EventNonZeroTerminated) {
-  BundleProvider bundle_provider(base::kPageSize);
+  BundleProvider bundle_provider(base::GetSysPageSize());
 
   BinaryWriter writer;
   ProtoTranslationTable* table = GetTable("android_seed_N2F62_3.10.49");
@@ -1612,10 +1616,12 @@
   // Prepare a buffer with 8 contiguous pages, with the above contents.
   static constexpr size_t kTestPages = 8;
 
-  std::unique_ptr<uint8_t[]> buf(new uint8_t[base::kPageSize * kTestPages]());
+  std::unique_ptr<uint8_t[]> buf(
+      new uint8_t[base::GetSysPageSize() * kTestPages]());
   for (size_t i = 0; i < kTestPages; i++) {
-    void* dest = buf.get() + (i * base::kPageSize);
-    memcpy(dest, static_cast<const void*>(test_page_order[i]), base::kPageSize);
+    void* dest = buf.get() + (i * base::GetSysPageSize());
+    memcpy(dest, static_cast<const void*>(test_page_order[i]),
+           base::GetSysPageSize());
   }
 
   ProtoTranslationTable* table = GetTable("synthetic");
@@ -1662,10 +1668,12 @@
   // Prepare a buffer with 8 contiguous pages, with the above contents.
   static constexpr size_t kTestPages = 8;
 
-  std::unique_ptr<uint8_t[]> buf(new uint8_t[base::kPageSize * kTestPages]());
+  std::unique_ptr<uint8_t[]> buf(
+      new uint8_t[base::GetSysPageSize() * kTestPages]());
   for (size_t i = 0; i < kTestPages; i++) {
-    void* dest = buf.get() + (i * base::kPageSize);
-    memcpy(dest, static_cast<const void*>(test_page_order[i]), base::kPageSize);
+    void* dest = buf.get() + (i * base::GetSysPageSize());
+    memcpy(dest, static_cast<const void*>(test_page_order[i]),
+           base::GetSysPageSize());
   }
 
   ProtoTranslationTable* table = GetTable("synthetic");
@@ -1881,7 +1889,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -2365,7 +2373,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -2888,7 +2896,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_TRUE(page_header->lost_events);  // data loss
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -2994,7 +3002,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -3296,7 +3304,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -3341,7 +3349,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
@@ -3395,7 +3403,7 @@
   std::optional<CpuReader::PageHeader> page_header =
       CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
 
-  const uint8_t* page_end = page.get() + base::kPageSize;
+  const uint8_t* page_end = page.get() + base::GetSysPageSize();
   ASSERT_TRUE(page_header.has_value());
   EXPECT_FALSE(page_header->lost_events);
   EXPECT_LE(parse_pos + page_header->size, page_end);
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer.cc b/src/traced/probes/ftrace/ftrace_config_muxer.cc
index 0be1842..4d6019e 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer.cc
@@ -574,7 +574,7 @@
     requested_buffer_size_kb = kMaxPerCpuBufferSizeKb;
   }
 
-  size_t pages = requested_buffer_size_kb / (base::kPageSize / 1024);
+  size_t pages = requested_buffer_size_kb / (base::GetSysPageSize() / 1024);
   if (pages == 0)
     return 1;
 
diff --git a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
index 8e83c57..835488f 100644
--- a/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_config_muxer_unittest.cc
@@ -19,6 +19,7 @@
 #include <memory>
 
 #include "ftrace_config_muxer.h"
+#include "perfetto/ext/base/utils.h"
 #include "src/traced/probes/ftrace/atrace_wrapper.h"
 #include "src/traced/probes/ftrace/compact_sched.h"
 #include "src/traced/probes/ftrace/ftrace_procfs.h"
@@ -53,6 +54,10 @@
     "sys_read",
 };
 
+std::string PageSizeKb() {
+  return std::to_string(base::GetSysPageSize() / 1024);
+}
+
 class MockFtraceProcfs : public FtraceProcfs {
  public:
   MockFtraceProcfs() : FtraceProcfs("/root/") {
@@ -229,17 +234,22 @@
 };
 
 TEST_F(FtraceConfigMuxerTest, ComputeCpuBufferSizeInPages) {
-  static constexpr size_t kMaxBufSizeInPages = 16 * 1024u;
-  // No buffer size given: good default (128 pages = 2mb).
-  EXPECT_EQ(ComputeCpuBufferSizeInPages(0), 512u);
+  auto Pages = [](uint32_t kb) { return kb * 1024 / base::GetSysPageSize(); };
+  const size_t kMaxBufSizeInPages = Pages(64 * 1024);
+
+  // No buffer size given: good default (2mb).
+  EXPECT_EQ(ComputeCpuBufferSizeInPages(0), Pages(2048));
+
   // Buffer size given way too big: good default.
   EXPECT_EQ(ComputeCpuBufferSizeInPages(10 * 1024 * 1024), kMaxBufSizeInPages);
-  // The limit is 64mb per CPU, 512mb is too much.
   EXPECT_EQ(ComputeCpuBufferSizeInPages(512 * 1024), kMaxBufSizeInPages);
+
   // Your size ends up with less than 1 page per cpu -> 1 page.
   EXPECT_EQ(ComputeCpuBufferSizeInPages(3), 1u);
+
   // You picked a good size -> your size rounded to nearest page.
-  EXPECT_EQ(ComputeCpuBufferSizeInPages(42), 10u);
+  EXPECT_EQ(ComputeCpuBufferSizeInPages(42),
+            42 * 1024 / base::GetSysPageSize());
 }
 
 TEST_F(FtraceConfigMuxerTest, GenericSyscallFiltering) {
@@ -613,7 +623,7 @@
   EXPECT_CALL(ftrace,
               WriteToFile("/root/events/sched/sched_switch/enable", "0"));
   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "0"));
-  EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "4"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", PageSizeKb()));
   EXPECT_CALL(ftrace, WriteToFile("/root/events/enable", "0"));
   EXPECT_CALL(ftrace, ClearFile("/root/trace"));
   EXPECT_CALL(ftrace, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
@@ -1128,7 +1138,7 @@
   EXPECT_CALL(ftrace, AppendToFile("/root/set_event", "!cgroup:cgroup_mkdir"))
       .WillOnce(Return(true));
   EXPECT_CALL(ftrace, WriteToFile("/root/tracing_on", "0"));
-  EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", "4"));
+  EXPECT_CALL(ftrace, WriteToFile("/root/buffer_size_kb", PageSizeKb()));
   EXPECT_CALL(ftrace, WriteToFile("/root/events/enable", "0"));
   EXPECT_CALL(ftrace, ClearFile("/root/trace"));
   EXPECT_CALL(ftrace, ClearFile(MatchesRegex("/root/per_cpu/cpu[0-9]/trace")));
diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
index 5ffc6c2..c9a5ca1 100644
--- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
@@ -58,6 +58,10 @@
 constexpr char kFooEnablePath[] = "/root/events/group/foo/enable";
 constexpr char kBarEnablePath[] = "/root/events/group/bar/enable";
 
+std::string PageSizeKb() {
+  return std::to_string(base::GetSysPageSize() / 1024);
+}
+
 class MockTaskRunner : public base::TaskRunner {
  public:
   MOCK_METHOD(void, PostTask, (std::function<void()>), (override));
@@ -346,7 +350,8 @@
   // State clearing on tracing teardown.
   EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "0"));
   EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "0"));
-  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "4"));
+  EXPECT_CALL(*controller->procfs(),
+              WriteToFile("/root/buffer_size_kb", PageSizeKb()));
   EXPECT_CALL(*controller->procfs(), WriteToFile("/root/events/enable", "0"));
   EXPECT_CALL(*controller->procfs(), ClearFile("/root/trace"))
       .WillOnce(Return(true));
@@ -401,7 +406,8 @@
   EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "0"));
   EXPECT_CALL(*controller->procfs(), WriteToFile(kBarEnablePath, "0"));
   EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "0"));
-  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "4"));
+  EXPECT_CALL(*controller->procfs(),
+              WriteToFile("/root/buffer_size_kb", PageSizeKb()));
   EXPECT_CALL(*controller->procfs(), WriteToFile("/root/events/enable", "0"));
   EXPECT_CALL(*controller->procfs(), ClearFile("/root/trace"))
       .WillOnce(Return(true));
@@ -435,7 +441,8 @@
   // State clearing on tracing teardown.
   EXPECT_CALL(*controller->procfs(), WriteToFile(kFooEnablePath, "0"));
   EXPECT_CALL(*controller->procfs(), WriteToFile("/root/tracing_on", "0"));
-  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "4"));
+  EXPECT_CALL(*controller->procfs(),
+              WriteToFile("/root/buffer_size_kb", PageSizeKb()));
   EXPECT_CALL(*controller->procfs(), WriteToFile("/root/events/enable", "0"));
   EXPECT_CALL(*controller->procfs(), ClearFile("/root/trace"))
       .WillOnce(Return(true));
@@ -456,7 +463,8 @@
 
   // Every time a fake data source is destroyed, the controller will reset the
   // buffer size to a single page.
-  EXPECT_CALL(*controller->procfs(), WriteToFile("/root/buffer_size_kb", "4"))
+  EXPECT_CALL(*controller->procfs(),
+              WriteToFile("/root/buffer_size_kb", PageSizeKb()))
       .Times(AnyNumber());
 
   {
@@ -501,9 +509,9 @@
   {
     // You picked a good size -> your size rounded to nearest page.
     EXPECT_CALL(*controller->procfs(),
-                WriteToFile("/root/buffer_size_kb", "40"));
+                WriteToFile("/root/buffer_size_kb", "64"));
     FtraceConfig config = CreateFtraceConfig({"group/foo"});
-    config.set_buffer_size_kb(42);
+    config.set_buffer_size_kb(65);
     auto data_source = controller->AddFakeDataSource(config);
     ASSERT_TRUE(controller->StartDataSource(data_source.get()));
   }
@@ -511,10 +519,10 @@
   {
     // You picked a good size -> your size rounded to nearest page.
     EXPECT_CALL(*controller->procfs(),
-                WriteToFile("/root/buffer_size_kb", "40"));
+                WriteToFile("/root/buffer_size_kb", "64"));
     FtraceConfig config = CreateFtraceConfig({"group/foo"});
     ON_CALL(*controller->procfs(), NumberOfCpus()).WillByDefault(Return(2));
-    config.set_buffer_size_kb(42);
+    config.set_buffer_size_kb(65);
     auto data_source = controller->AddFakeDataSource(config);
     ASSERT_TRUE(controller->StartDataSource(data_source.get()));
   }
@@ -681,8 +689,9 @@
   EXPECT_CALL(
       *controller->GetInstanceMockProcfs("secondary"),
       WriteToFile("/root/instances/secondary/events/group/foo/enable", "0"));
-  EXPECT_CALL(*controller->GetInstanceMockProcfs("secondary"),
-              WriteToFile("/root/instances/secondary/buffer_size_kb", "4"));
+  EXPECT_CALL(
+      *controller->GetInstanceMockProcfs("secondary"),
+      WriteToFile("/root/instances/secondary/buffer_size_kb", PageSizeKb()));
 
   controller->RemoveDataSource(data_source.get());
 
diff --git a/src/traced/probes/ftrace/ftrace_procfs.cc b/src/traced/probes/ftrace/ftrace_procfs.cc
index 396ba60..f092468 100644
--- a/src/traced/probes/ftrace/ftrace_procfs.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs.cc
@@ -415,12 +415,12 @@
 }
 
 bool FtraceProcfs::SetCpuBufferSizeInPages(size_t pages) {
-  if (pages * base::kPageSize > 1 * 1024 * 1024 * 1024) {
+  if (pages * base::GetSysPageSize() > 1 * 1024 * 1024 * 1024) {
     PERFETTO_ELOG("Tried to set the per CPU buffer size to more than 1gb.");
     return false;
   }
   std::string path = root_ + "buffer_size_kb";
-  return WriteNumberToFile(path, pages * (base::kPageSize / 1024ul));
+  return WriteNumberToFile(path, pages * (base::GetSysPageSize() / 1024ul));
 }
 
 bool FtraceProcfs::GetTracingOn() {
diff --git a/src/traced/probes/ftrace/test/cpu_reader_support.cc b/src/traced/probes/ftrace/test/cpu_reader_support.cc
index 020298b..dc386ff 100644
--- a/src/traced/probes/ftrace/test/cpu_reader_support.cc
+++ b/src/traced/probes/ftrace/test/cpu_reader_support.cc
@@ -54,9 +54,9 @@
 }
 
 std::unique_ptr<uint8_t[]> PageFromXxd(const std::string& text) {
-  auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[base::kPageSize]);
+  auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[base::GetSysPageSize()]);
   const char* ptr = text.data();
-  memset(buffer.get(), 0xfa, base::kPageSize);
+  memset(buffer.get(), 0xfa, base::GetSysPageSize());
   uint8_t* out = buffer.get();
   while (*ptr != '\0') {
     if (*(ptr++) != ':')
diff --git a/src/tracing/core/trace_buffer_unittest.cc b/src/tracing/core/trace_buffer_unittest.cc
index 6fc36f6..b98ecf0 100644
--- a/src/tracing/core/trace_buffer_unittest.cc
+++ b/src/tracing/core/trace_buffer_unittest.cc
@@ -1992,16 +1992,16 @@
 
 TEST_F(TraceBufferTest, Clone_CommitOnlyUsedSize) {
   const size_t kPages = 32;
-  const size_t kPageSize = base::GetSysPageSize();
-  ResetBuffer(kPageSize * kPages);
+  const size_t page_size = base::GetSysPageSize();
+  ResetBuffer(page_size * kPages);
   CreateChunk(ProducerID(1), WriterID(0), ChunkID(0))
       .AddPacket(1024, static_cast<char>('a'))
       .CopyIntoTraceBuffer();
 
   using base::vm_test_utils::IsMapped;
   auto is_only_first_page_mapped = [&](const TraceBuffer& buf) {
-    bool first_mapped = IsMapped(GetBufData(buf), kPageSize);
-    bool rest_mapped = IsMapped(GetBufData(buf) + kPageSize, kPages - 1);
+    bool first_mapped = IsMapped(GetBufData(buf), page_size);
+    bool rest_mapped = IsMapped(GetBufData(buf) + page_size, kPages - 1);
     return first_mapped && !rest_mapped;
   };
 
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index d975a29..1e4783a 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -2013,6 +2013,9 @@
         if (!weak_this)
           return;
         TracingSession* session = weak_this->GetTracingSession(tsid);
+        if (!session) {
+          return;
+        }
         session->final_flush_outcome = success
                                            ? TraceStats::FINAL_FLUSH_SUCCEEDED
                                            : TraceStats::FINAL_FLUSH_FAILED;
diff --git a/src/tracing/core/tracing_service_impl_unittest.cc b/src/tracing/core/tracing_service_impl_unittest.cc
index fd578b8..c024c82 100644
--- a/src/tracing/core/tracing_service_impl_unittest.cc
+++ b/src/tracing/core/tracing_service_impl_unittest.cc
@@ -5158,4 +5158,55 @@
                                              Eq("B|1023|payload"))))));
 }
 
+// This is a regression test for https://b.corp.google.com/issues/307601836. The
+// test covers the case of a consumer disconnecting while the tracing session is
+// executing the final flush.
+TEST_F(TracingServiceImplTest, ConsumerDisconnectionRacesFlushAndDisable) {
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+
+  producer->RegisterDataSource("ds");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(128);
+  auto* trigger_config = trace_config.mutable_trigger_config();
+  trigger_config->set_trigger_mode(TraceConfig::TriggerConfig::STOP_TRACING);
+  trigger_config->set_trigger_timeout_ms(100000);
+  auto* trigger = trigger_config->add_triggers();
+  trigger->set_name("trigger_name");
+  auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+  ds_cfg->set_name("ds");
+
+  consumer->EnableTracing(trace_config);
+  producer->WaitForTracingSetup();
+  producer->WaitForDataSourceSetup("ds");
+  producer->WaitForDataSourceStart("ds");
+
+  auto writer1 = producer->CreateTraceWriter("ds");
+
+  auto producer_flush_cb = [&](FlushRequestID flush_req_id,
+                               const DataSourceInstanceID* /*id*/, size_t,
+                               FlushFlags) {
+    // Notify the tracing service that the flush is complete.
+    producer->endpoint()->NotifyFlushComplete(flush_req_id);
+    // Also disconnect the consumer (this terminates the tracing session). The
+    // consumer disconnection is postponed with a PostTask(). The goal is to run
+    // the lambda inside TracingServiceImpl::FlushAndDisableTracing() with an
+    // empty `tracing_sessions_` map.
+    task_runner.PostTask([&]() { consumer.reset(); });
+  };
+  EXPECT_CALL(*producer, Flush(_, _, _, _)).WillOnce(Invoke(producer_flush_cb));
+
+  // Cause the tracing session to stop. Note that
+  // TracingServiceImpl::FlushAndDisableTracing() is also called when
+  // duration_ms expires, but in a test it's faster to use a trigger.
+  producer->endpoint()->ActivateTriggers({"trigger_name"});
+  producer->WaitForDataSourceStop("ds");
+
+  task_runner.RunUntilIdle();
+}
+
 }  // namespace perfetto
diff --git a/test/data/chrome_example_wikipedia.perfetto_trace.gz.sha256 b/test/data/chrome_example_wikipedia.perfetto_trace.gz.sha256
new file mode 100644
index 0000000..a3da3d9
--- /dev/null
+++ b/test/data/chrome_example_wikipedia.perfetto_trace.gz.sha256
@@ -0,0 +1 @@
+7401f67e30cb025b113cef1db53d7e154705e688e33142f65fd7875df26f2cf0
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
index 3af03af..7481c80 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_select_slice_with_flows.png.sha256
@@ -1 +1 @@
-b51988f52a96b6576e714111c96018b8f13541a5ccd44b8bf9b1989185edd515
\ No newline at end of file
+1fa69f1c7b6098b1e7ab944f4900a3b52984522e18ce94a6c68f8c46c6c06154
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
index e268e28..e138b33 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_1.png.sha256
@@ -1 +1 @@
-c753a17a466814841035cd006716128187d838c6c00fb7c600f810b6b36a7be9
\ No newline at end of file
+1dd5883861cff0c03811e5e97c479097771b8ab6ee97b5320593eb5f850c9a25
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
index a26ddd5..5aee346 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_show_dialog_2.png.sha256
@@ -1 +1 @@
-784304442fea618ab4ba75c16ccbc3d08fcfbe3ba19d7f7b1a470aa520d801a1
\ No newline at end of file
+f93e24fcd03c2f69570c4a01bc8df04b05260b35983d479ab24f7b912ebccd4d
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
index 36fadce..766073d 100644
--- a/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_invalid_trace_from_blank_page.png.sha256
@@ -1 +1 @@
-ac4b7c10fb3ca19724f2f66b3b4b013cec90bac60d06d09b2bf96e30944750d8
\ No newline at end of file
+133e44411f05ea57299a67258d307ec06d381c2852143bf012059365fd2a7716
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
index 4afc696..5949d49 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_invalid_trace.png.sha256
@@ -1 +1 @@
-3b624767a7b2118e71fb4efa948ffe2021f1e59e30ddfadf0a07fa22203fbb67
\ No newline at end of file
+f650a9a968e978dfa37f900cc5509bdc9118e8f3c74197164982285acde6149f
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/include_index.py b/test/trace_processor/diff_tests/include_index.py
index fbf8a66..5def22b 100644
--- a/test/trace_processor/diff_tests/include_index.py
+++ b/test/trace_processor/diff_tests/include_index.py
@@ -87,31 +87,39 @@
 from diff_tests.parser.translated_args.tests import TranslatedArgs
 from diff_tests.parser.ufs.tests import Ufs
 from diff_tests.stdlib.android.tests import AndroidStdlib
-from diff_tests.stdlib.common.tests import StdlibCommon
 from diff_tests.stdlib.chrome.tests import ChromeStdlib
 from diff_tests.stdlib.chrome.tests_chrome_interactions import ChromeInteractions
 from diff_tests.stdlib.chrome.tests_scroll_jank import ChromeScrollJankStdlib
 from diff_tests.stdlib.common.tests import StdlibCommon
+from diff_tests.stdlib.common.tests import StdlibCommon
 from diff_tests.stdlib.dynamic_tables.tests import DynamicTables
+from diff_tests.stdlib.intervals.tests import StdlibIntervals
 from diff_tests.stdlib.linux.tests import LinuxStdlib
 from diff_tests.stdlib.pkvm.tests import Pkvm
-from diff_tests.stdlib.intervals.tests import StdlibIntervals
+from diff_tests.stdlib.prelude.math_functions_tests import PreludeMathFunctions
+from diff_tests.stdlib.prelude.pprof_functions_tests import PreludePprofFunctions
+from diff_tests.stdlib.prelude.window_functions_tests import PreludeWindowFunctions
+from diff_tests.stdlib.prelude.slices_tests import PreludeSlices
 from diff_tests.stdlib.sched.tests import StdlibSched
 from diff_tests.stdlib.slices.tests import Slices
 from diff_tests.stdlib.span_join.tests_left_join import SpanJoinLeftJoin
 from diff_tests.stdlib.span_join.tests_outer_join import SpanJoinOuterJoin
 from diff_tests.stdlib.span_join.tests_regression import SpanJoinRegression
 from diff_tests.stdlib.span_join.tests_smoke import SpanJoinSmoke
+from diff_tests.stdlib.tests import StdlibSmoke
 from diff_tests.stdlib.timestamps.tests import Timestamps
-from diff_tests.syntax.functions.tests import Functions
-from diff_tests.syntax.perfetto_sql.tests import PerfettoSql
+from diff_tests.syntax.function_tests import PerfettoFunction
+from diff_tests.syntax.include_tests import PerfettoInclude
+from diff_tests.syntax.macro_tests import PerfettoMacro
+from diff_tests.syntax.table_function_tests import PerfettoTableFunction
+from diff_tests.syntax.table_tests import PerfettoTable
+from diff_tests.syntax.view_tests import PerfettoView
 from diff_tests.tables.tests import Tables
 from diff_tests.tables.tests_counters import TablesCounters
 from diff_tests.tables.tests_sched import TablesSched
 
 sys.path.pop()
 
-
 def fetch_all_diff_tests(index_path: str) -> List['testing.TestCase']:
   parser_tests = [
       *AndroidBugreport(index_path, 'parser/android',
@@ -218,7 +226,16 @@
       *DynamicTables(index_path, 'stdlib/dynamic_tables',
                      'DynamicTables').fetch(),
       *LinuxStdlib(index_path, 'stdlib/linux', 'LinuxStdlib').fetch(),
+      *PreludeMathFunctions(index_path, 'stdlib/prelude',
+                            'PreludeMathFunctions').fetch(),
+      *PreludePprofFunctions(index_path, 'stdlib/prelude',
+                             'PreludePprofFunctions').fetch(),
+      *PreludeWindowFunctions(index_path, 'stdlib/prelude',
+                              'PreludeWindowFunctions').fetch(),
       *Pkvm(index_path, 'stdlib/pkvm', 'Pkvm').fetch(),
+      *PreludeSlices(index_path, 'stdlib/prelude', 'PreludeSlices').fetch(),
+      *StdlibSmoke(index_path, 'stdlib', 'StdlibSmoke').fetch(),
+      *StdlibCommon(index_path, 'stdlib/common', 'StdlibCommon').fetch(),
       *Slices(index_path, 'stdlib/slices', 'Slices').fetch(),
       *SpanJoinLeftJoin(index_path, 'stdlib/span_join',
                         'SpanJoinLeftJoin').fetch(),
@@ -234,8 +251,13 @@
   ]
 
   syntax_tests = [
-      *Functions(index_path, 'syntax/functions', 'Functions').fetch(),
-      *PerfettoSql(index_path, 'syntax/perfetto_sql', 'PerfettoSql').fetch(),
+      *PerfettoFunction(index_path, 'syntax', 'PerfettoFunction').fetch(),
+      *PerfettoInclude(index_path, 'syntax', 'PerfettoInclude').fetch(),
+      *PerfettoMacro(index_path, 'syntax', 'PerfettoMacro').fetch(),
+      *PerfettoTable(index_path, 'syntax', 'PerfettoTable').fetch(),
+      *PerfettoTableFunction(index_path, 'syntax',
+                             'PerfettoTableFunction').fetch(),
+      *PerfettoView(index_path, 'syntax', 'PerfettoView').fetch(),
   ]
 
   return parser_tests + metrics_tests + stdlib_tests + syntax_tests + [
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup.out b/test/trace_processor/diff_tests/metrics/startup/android_startup.out
index 1dc4c36..9dfc289 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup.out
@@ -67,6 +67,10 @@
       installd_dur_ns: 0
       dex2oat_dur_ns: 0
     }
+    slow_start_reason_detailed {
+      reason: "Main Thread - Time spent in Runnable state"
+      details: " target 15% actual 74.07% [ longest_chunk: start_s 3.0e-08 dur_ms 8.0e-0 thread_id 3 thread_name com.google.android.calendar ] [ extra_info:  launches_dur_ms 0.0001 runnable_dur_ms 8.0e-0 R_sum_dur_ms 8.0e-0 R+(Preempted)_sum_dur_ms 0.0 ]"
+    } 
     startup_type: "warm"
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out
index d99f957..9b6e742 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution.out
@@ -134,5 +134,14 @@
     slow_start_reason: "GC Activity"
     slow_start_reason: "Main Thread - Time spent in OpenDexFilesFromOat*"
     slow_start_reason: "Main Thread - Binder transactions blocked"
+    slow_start_reason_detailed {
+      reason: "GC Activity"
+    }
+    slow_start_reason_detailed {
+      reason: "Main Thread - Time spent in OpenDexFilesFromOat*"
+    }
+    slow_start_reason_detailed {
+      reason: "Main Thread - Binder transactions blocked"
+    }
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out
index db0495e..3e8ff27 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_attribution_slow.out
@@ -98,5 +98,15 @@
     slow_start_reason: "GC Activity"
     slow_start_reason: "JIT Activity"
     slow_start_reason: "JIT compiled methods"
+    slow_start_reason_detailed {
+      reason: "GC Activity"
+    }
+    slow_start_reason_detailed {
+      reason: "JIT Activity"
+      details: " target 100ms actual 20000.ms [ longest_chunk: start_s 154.99 dur_ms 10000. thread_id 4 thread_name Jit thread pool ]"
+    }
+    slow_start_reason_detailed {
+      reason: "JIT compiled methods"
+    }
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out
index 8dfb3b3..f40578d 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown.out
@@ -119,6 +119,27 @@
     slow_start_reason: "Time spent in bindApplication"
     slow_start_reason: "Time spent in view inflation"
     slow_start_reason: "Time spent in ResourcesManager#getResources"
+    slow_start_reason_detailed {
+      reason: "No baseline or cloud profiles"
+      details: " target FALSE actual TRUE [ longest_chunk: start_s 154.0 dur_ms 1000.0 thread_id -1 thread_name com.google.android.calendar ] [ extra_info:  slice_name location=/system/framework/oat/arm/com.google.android.calendar.odex status=up-to-date filter=speed reason=install-dm ]"
+    }
+    slow_start_reason_detailed {
+      reason: "Optimized artifacts missing, run from apk"
+    }
+    slow_start_reason_detailed {
+      reason: "Time spent in bindApplication"
+    }
+    slow_start_reason_detailed {
+      reason: "Time spent in view inflation"
+    }
+    slow_start_reason_detailed {
+      reason: "Time spent in ResourcesManager#getResources"
+      details: " target 130ms actual 1000.0ms [ longest_chunk: start_s 138.0 dur_ms 1000.0 thread_id 3 thread_name com.google.android.calendar ]"
+    }
+    slow_start_reason_detailed {
+      reason: "Potential CPU contention with another process"
+      details: " target 100ms actual 5000.0ms most_active_process_for_launch init [ longest_chunk: start_s 155.0 dur_ms 5000.0 thread_id 3 thread_name com.google.android.calendar ] [ extra_info:  runnable_dur_ms 5000.0 R_sum_dur_ms 5000.0 R+(Preempted)_sum_dur 0 ]"
+    }
     startup_type: "cold"
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out
index a10ac3f..891060b 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_breakdown_slow.out
@@ -118,6 +118,23 @@
     slow_start_reason: "Time spent in bindApplication"
     slow_start_reason: "Time spent in view inflation"
     slow_start_reason: "Time spent in ResourcesManager#getResources"
+    slow_start_reason_detailed {
+      reason: "Optimized artifacts missing, run from apk"
+    }
+    slow_start_reason_detailed {
+      reason: "Time spent in bindApplication"
+    }
+    slow_start_reason_detailed {
+      reason: "Time spent in view inflation"
+    }
+    slow_start_reason_detailed {
+      reason: "Time spent in ResourcesManager#getResources"
+      details: " target 130ms actual 5000.0ms [ longest_chunk: start_s 137.0 dur_ms 5000.0 thread_id 3 thread_name com.google.android.calendar ]"
+    }
+    slow_start_reason_detailed {
+      reason: "Potential CPU contention with another process"
+      details: " target 100ms actual 5000.0ms most_active_process_for_launch init [ longest_chunk: start_s 155.0 dur_ms 5000.0 thread_id 3 thread_name com.google.android.calendar ] [ extra_info:  runnable_dur_ms 5000.0 R_sum_dur_ms 5000.0 R+(Preempted)_sum_dur 0 ]"
+    }
     startup_type: "cold"
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out
index c0b245c..02e142c 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_broadcast_multiple.out
@@ -31,5 +31,11 @@
     }
     slow_start_reason: "Broadcast dispatched count"
     slow_start_reason: "Broadcast received count"
+    slow_start_reason_detailed {
+      reason: "Broadcast dispatched count"
+    }
+    slow_start_reason_detailed {
+      reason: "Broadcast received count"
+    }
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out
index f2b6285..1fd7f2e 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat.out
@@ -62,6 +62,9 @@
       dex2oat_dur_ns: 5
     }
     slow_start_reason: "dex2oat running during launch"
+    slow_start_reason_detailed {
+      reason: "dex2oat running during launch"
+    }
   }
   startup {
     startup_id: 3
@@ -95,6 +98,9 @@
       dex2oat_dur_ns: 0
     }
     slow_start_reason: "installd running during launch"
+    slow_start_reason_detailed {
+      reason: "installd running during launch"
+    }
   }
   startup {
     startup_id: 4
@@ -130,5 +136,11 @@
     }
     slow_start_reason: "dex2oat running during launch"
     slow_start_reason: "installd running during launch"
+    slow_start_reason_detailed {
+      reason: "dex2oat running during launch"
+    }
+    slow_start_reason_detailed {
+      reason: "installd running during launch"
+    }
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out
index 18039c2..91e0d5a 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_installd_dex2oat_slow.out
@@ -62,6 +62,9 @@
       dex2oat_dur_ns: 5000000000
     }
     slow_start_reason: "dex2oat running during launch"
+    slow_start_reason_detailed {
+      reason: "dex2oat running during launch"
+    }
   }
   startup {
     startup_id: 3
@@ -98,6 +101,15 @@
     slow_start_reason: "dex2oat running during launch"
     slow_start_reason: "installd running during launch"
     slow_start_reason: "Startup running concurrent to launch"
+    slow_start_reason_detailed {
+      reason: "dex2oat running during launch"
+    }
+    slow_start_reason_detailed {
+      reason: "installd running during launch"
+    }
+    slow_start_reason_detailed {
+      reason: "Startup running concurrent to launch"
+    }
     startup_concurrent_to_launch: "com.google.android.gm"
   }
   startup {
@@ -133,6 +145,15 @@
     slow_start_reason: "dex2oat running during launch"
     slow_start_reason: "installd running during launch"
     slow_start_reason: "Startup running concurrent to launch"
+    slow_start_reason_detailed {
+      reason: "dex2oat running during launch"
+    }
+    slow_start_reason_detailed {
+      reason: "installd running during launch"
+    }
+    slow_start_reason_detailed {
+      reason: "Startup running concurrent to launch"
+    }
     startup_concurrent_to_launch: "com.google.android.deskclock"
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out
index aa4ec05..3e5f6a6 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_lock_contention_slow.out
@@ -72,6 +72,15 @@
     slow_start_reason: "Time spent in bindApplication"
     slow_start_reason: "Main Thread - Lock contention"
     slow_start_reason: "Main Thread - Monitor contention"
+    slow_start_reason_detailed {
+      reason: "Time spent in bindApplication"
+    }
+    slow_start_reason_detailed {
+      reason: "Main Thread - Lock contention"
+    }
+    slow_start_reason_detailed {
+      reason: "Main Thread - Monitor contention"
+    }
     startup_type: "cold"
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out
index d4de48e..e56ae6f 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_minsdk33.out
@@ -30,6 +30,9 @@
       dex2oat_dur_ns: 0
     }
     slow_start_reason: "App in debuggable mode"
+    slow_start_reason_detailed {
+      reason: "App in debuggable mode"
+    }
   }
   startup {
     startup_id: 2
@@ -83,5 +86,8 @@
     }
     startup_type: "hot"
     slow_start_reason: "App in debuggable mode"
+    slow_start_reason_detailed {
+      reason: "App in debuggable mode"
+    }
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out
index 21d909d..fb10a32 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_process_track.out
@@ -66,6 +66,10 @@
       dex2oat_dur_ns: 0
     }
     startup_type: "cold"
+    slow_start_reason_detailed {
+      reason: "Main Thread - Time spent in Runnable state"
+      details: " target 15% actual 57.14% [ longest_chunk: start_s 3.0e-09 dur_ms 4.0e-0 thread_id 3 thread_name com.google.android.calendar ] [ extra_info:  launches_dur_ms 7.0e-0 runnable_dur_ms 4.0e-0 R_sum_dur_ms 4.0e-0 R+(Preempted)_sum_dur_ms 0.0 ]"
+    }
   }
   startup {
     startup_id: 2
@@ -135,5 +139,9 @@
       dex2oat_dur_ns: 0
     }
     startup_type: "cold"
+    slow_start_reason_detailed {
+      reason: "Main Thread - Time spent in Runnable state"
+      details: " target 15% actual 57.14% [ longest_chunk: start_s 1.03e-07 dur_ms 4.0e-0 thread_id 4 thread_name com.google.android.calendar ] [ extra_info:  launches_dur_ms 7.0e-0 runnable_dur_ms 4.0e-0 R_sum_dur_ms 4.0e-0 R+(Preempted)_sum_dur_ms 0.0 ]"
+    }
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out
index 78e1908..4cdc5e9 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_slow.out
@@ -70,5 +70,19 @@
     startup_type: "warm"
     slow_start_reason: "Main Thread - Time spent in interruptible sleep state"
     slow_start_reason: "Main Thread - Time spent in Blocking I/O"
+    slow_start_reason_detailed {
+      reason: "Main Thread - Time spent in Runnable state"
+      details: " target 15% actual 74.07% [ longest_chunk: start_s 30.0 dur_ms 80000. thread_id 3 thread_name com.google.android.calendar ] [ extra_info:  launches_dur_ms 108000 runnable_dur_ms 80000. R_sum_dur_ms 80000. R+(Preempted)_sum_dur_ms 0.0 ]"
+    }
+    slow_start_reason_detailed {
+      reason: "Main Thread - Time spent in interruptible sleep state"
+    }
+    slow_start_reason_detailed {
+      reason: "Main Thread - Time spent in Blocking I/O"
+    }
+    slow_start_reason_detailed {
+      reason: "Potential CPU contention with another process"
+      details: " target 100ms actual 80000.ms most_active_process_for_launch init [ longest_chunk: start_s 30.0 dur_ms 80000. thread_id 3 thread_name com.google.android.calendar ] [ extra_info:  runnable_dur_ms 80000. R_sum_dur_ms 80000. R+(Preempted)_sum_dur 0 ]"
+    }
   }
 }
diff --git a/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out b/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out
index ba5d58f..9686456 100644
--- a/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out
+++ b/test/trace_processor/diff_tests/metrics/startup/android_startup_unlock.out
@@ -30,5 +30,8 @@
       dex2oat_dur_ns: 0
     }
     slow_start_reason: "Unlock running during launch"
+    slow_start_reason_detailed {
+      reason: "Unlock running during launch"
+    }
   }
 }
diff --git a/test/trace_processor/diff_tests/stdlib/android/android_battery_stats_state.out b/test/trace_processor/diff_tests/stdlib/android/android_battery_stats_state.out
deleted file mode 100644
index eb16850..0000000
--- a/test/trace_processor/diff_tests/stdlib/android/android_battery_stats_state.out
+++ /dev/null
@@ -1,4 +0,0 @@
-"ts","track_name","value","value_name","dur"
-1000,"battery_stats.audio",1,"active",-1
-1000,"battery_stats.data_conn",13,"4G (LTE)",3000
-4000,"battery_stats.data_conn",20,"5G (NR)",-1
diff --git a/test/trace_processor/diff_tests/stdlib/android/tests.py b/test/trace_processor/diff_tests/stdlib/android/tests.py
index 56bcf3a..3f2800f 100644
--- a/test/trace_processor/diff_tests/stdlib/android/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/android/tests.py
@@ -105,7 +105,12 @@
         SELECT * FROM android_battery_stats_state
         ORDER BY ts, track_name;
         """,
-        out=Path('android_battery_stats_state.out'))
+        out=Csv("""
+        "ts","dur","track_name","value","value_name"
+        1000,-1,"battery_stats.audio",1,"active"
+        1000,3000,"battery_stats.data_conn",13,"4G (LTE)"
+        4000,-1,"battery_stats.data_conn",20,"5G (NR)"
+        """))
 
   def test_anrs(self):
     return DiffTestBlueprint(
@@ -842,8 +847,8 @@
         """))
 
   def test_android_dvfs_counters(self):
-      return DiffTestBlueprint(
-          trace=TextProto(r"""
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
           packet {
             ftrace_events {
               cpu: 0
@@ -876,11 +881,11 @@
             trusted_packet_sequence_id: 2
           }
          """),
-         query="""
+        query="""
          INCLUDE PERFETTO MODULE android.dvfs;
          SELECT * FROM android_dvfs_counters;
          """,
-         out=Csv("""
+        out=Csv("""
          "name","ts","value","dur"
          "domain@1 Frequency",200001000000,400000.000000,2000000
          "domain@1 Frequency",200003000000,1024000.000000,2000000
@@ -888,8 +893,8 @@
          """))
 
   def test_android_dvfs_counter_stats(self):
-      return DiffTestBlueprint(
-          trace=TextProto(r"""
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
           packet {
             ftrace_events {
               cpu: 0
@@ -946,19 +951,19 @@
             trusted_packet_sequence_id: 2
           }
          """),
-         query="""
+        query="""
          INCLUDE PERFETTO MODULE android.dvfs;
          SELECT * FROM android_dvfs_counter_stats;
          """,
-         out=Csv("""
+        out=Csv("""
          "name","max","min","dur","wgt_avg"
          "bus_throughput Frequency",1014000.000000,553000.000000,4000000,783499.942375
          "domain@1 Frequency",1024000.000000,400000.000000,4000000,712000.078000
          """))
 
   def test_android_dvfs_counter_residency(self):
-      return DiffTestBlueprint(
-          trace=TextProto(r"""
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
           packet {
             ftrace_events {
               cpu: 0
@@ -991,11 +996,11 @@
             trusted_packet_sequence_id: 2
           }
          """),
-         query="""
+        query="""
          INCLUDE PERFETTO MODULE android.dvfs;
          SELECT * FROM android_dvfs_counter_residency;
          """,
-         out=Csv("""
+        out=Csv("""
          "name","value","dur","pct"
          "bus_throughput Frequency",553000.000000,2000000,50.000000
          "bus_throughput Frequency",1014000.000000,2000000,50.000000
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_check.py b/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_check.py
old mode 100644
new mode 100755
index 909ea1d..445356e
--- a/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_check.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_check.py
@@ -1,17 +1,7 @@
 #!/usr/bin/env python3
-# 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.
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 # Discarded events that do not get to GPU are invisible for UMA metric and
 # therefore should be excluded in trace-based metric. This tests ensures that's
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_helper.py b/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_helper.py
old mode 100644
new mode 100755
index 77b6e05..07a7b8b
--- a/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_helper.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/chrome_scroll_helper.py
@@ -1,17 +1,7 @@
 #!/usr/bin/env python3
-# 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.
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 # Discarded events that do not get to GPU are invisible for UMA metric and
 # therefore should be excluded in trace-based metric. This tests ensures that's
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests.py b/test/trace_processor/diff_tests/stdlib/chrome/tests.py
old mode 100644
new mode 100755
index cc709b1..ddc088e
--- a/test/trace_processor/diff_tests/stdlib/chrome/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests.py
@@ -1,17 +1,7 @@
 #!/usr/bin/env python3
-# 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 a
-#
-#      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.
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests_chrome_interactions.py b/test/trace_processor/diff_tests/stdlib/chrome/tests_chrome_interactions.py
old mode 100644
new mode 100755
index 5019e54..5845dbb
--- a/test/trace_processor/diff_tests/stdlib/chrome/tests_chrome_interactions.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests_chrome_interactions.py
@@ -1,17 +1,7 @@
 #!/usr/bin/env python3
-# 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 a
-#
-#      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.
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 from python.generators.diff_tests.testing import DataPath
 from python.generators.diff_tests.testing import Csv
@@ -46,5 +36,5 @@
         10,687434796215243,475000000,687435271215243,475000000,687435271215243,1
         11,687435970742243,763000000,687436733742243,852000000,687436822742243,1
         13,687438343638243,1005000000,687439348638243,1005000000,687439348638243,1
-        14,687440258111243,900000000,687441158111243,"[NULL]",0,1
+        14,687440258111243,900000000,687441158111243,"[NULL]","[NULL]",1
         """))
diff --git a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
old mode 100644
new mode 100755
index 4d23659..b9d19fd
--- a/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/stdlib/chrome/tests_scroll_jank.py
@@ -1,17 +1,7 @@
 #!/usr/bin/env python3
-# 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 a
-#
-#      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.
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
 
 from python.generators.diff_tests.testing import Path, DataPath, Metric
 from python.generators.diff_tests.testing import Csv, Json, TextProto
@@ -138,3 +128,26 @@
         1993,4687375224739,-81,-181
         1996,4687386343739,-66,-247
         """))
+
+  def test_scroll_jank_cause_map(self):
+    return DiffTestBlueprint(
+        trace=TextProto(''),
+        query="""
+        INCLUDE PERFETTO MODULE chrome.event_latency_description;
+        INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_cause_map;
+
+        SELECT
+          DISTINCT event_latency_stage
+        FROM chrome_scroll_jank_cause_descriptions
+        WHERE event_latency_stage NOT IN
+          (
+            SELECT
+              DISTINCT name
+            FROM chrome_event_latency_stage_descriptions
+          );
+        """,
+        # Empty output is expected to ensure that all scroll jank causes
+        # correspond to a valid EventLatency stage.
+        out=Csv("""
+        "event_latency_stage"
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/common/tests.py b/test/trace_processor/diff_tests/stdlib/common/tests.py
index a902b5b..8e85918 100644
--- a/test/trace_processor/diff_tests/stdlib/common/tests.py
+++ b/test/trace_processor/diff_tests/stdlib/common/tests.py
@@ -45,4 +45,116 @@
         "state","cpu","dur"
         "Running",1,50
         "Runnable","[NULL]",25
-        """))
\ No newline at end of file
+        """))
+
+  def test_spans_overlapping_dur_intersect_edge(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        INCLUDE PERFETTO MODULE common.timestamps;
+        SELECT SPANS_OVERLAPPING_DUR(0, 2, 1, 2) AS dur
+        """,
+        out=Csv("""
+        "dur"
+        1
+        """))
+
+  def test_spans_overlapping_dur_intersect_edge_reversed(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        INCLUDE PERFETTO MODULE common.timestamps;
+        SELECT SPANS_OVERLAPPING_DUR(1, 2, 0, 2) AS dur
+        """,
+        out=Csv("""
+        "dur"
+        1
+        """))
+
+  def test_spans_overlapping_dur_intersect_all(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        INCLUDE PERFETTO MODULE common.timestamps;
+        SELECT SPANS_OVERLAPPING_DUR(0, 3, 1, 1) AS dur
+        """,
+        out=Csv("""
+        "dur"
+        1
+        """))
+
+  def test_spans_overlapping_dur_intersect_all_reversed(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        INCLUDE PERFETTO MODULE common.timestamps;
+        SELECT SPANS_OVERLAPPING_DUR(1, 1, 0, 3) AS dur
+        """,
+        out=Csv("""
+        "dur"
+        1
+        """))
+
+  def test_spans_overlapping_dur_no_intersect(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        INCLUDE PERFETTO MODULE common.timestamps;
+        SELECT SPANS_OVERLAPPING_DUR(0, 1, 2, 1) AS dur
+        """,
+        out=Csv("""
+        "dur"
+        0
+        """))
+
+  def test_spans_overlapping_dur_no_intersect_reversed(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        INCLUDE PERFETTO MODULE common.timestamps;
+        SELECT SPANS_OVERLAPPING_DUR(2, 1, 0, 1) AS dur
+        """,
+        out=Csv("""
+        "dur"
+        0
+        """))
+
+  def test_spans_overlapping_dur_negative_dur(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        INCLUDE PERFETTO MODULE common.timestamps;
+        SELECT SPANS_OVERLAPPING_DUR(0, -1, 0, 1) AS dur
+        """,
+        out=Csv("""
+        "dur"
+        0
+        """))
+
+  def test_spans_overlapping_dur_negative_dur_reversed(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        INCLUDE PERFETTO MODULE common.timestamps;
+        SELECT SPANS_OVERLAPPING_DUR(0, 1, 0, -1) AS dur
+        """,
+        out=Csv("""
+        "dur"
+        0
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/prelude/math_functions_tests.py b/test/trace_processor/diff_tests/stdlib/prelude/math_functions_tests.py
new file mode 100644
index 0000000..edf9877
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/prelude/math_functions_tests.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+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
+
+
+class PreludeMathFunctions(TestSuite):
+
+  def test_math_ln_function(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT
+          CAST(LN(1) * 1000 AS INTEGER) AS valid,
+          LN("as") AS invalid_str,
+          LN(NULL) AS invalid_null
+        """,
+        out=Csv("""
+        "valid","invalid_str","invalid_null"
+        0,"[NULL]","[NULL]"
+        """))
+
+  def test_math_exp_function(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT
+          CAST(EXP(1) * 1000 AS INTEGER) AS valid,
+          EXP("asd") AS invalid_str,
+          EXP(NULL) AS invalid_null
+        """,
+        out=Csv("""
+        "valid","invalid_str","invalid_null"
+        2718,"[NULL]","[NULL]"
+        """))
+
+  def test_math_sqrt_function(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT
+          CAST(SQRT(4) AS INTEGER) AS valid,
+          SQRT("asd") AS invalid_str,
+          SQRT(NULL) AS invalid_null
+        """,
+        out=Csv("""
+        "valid","invalid_str","invalid_null"
+        2,"[NULL]","[NULL]"
+        """))
+
+  def test_math_functions(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT
+          CAST(SQRT(EXP(LN(1))) AS INTEGER) AS valid,
+          SQRT(EXP(LN("asd"))) AS invalid_str,
+          SQRT(EXP(LN(NULL))) AS invalid_null
+        """,
+        out=Csv("""
+        "valid","invalid_str","invalid_null"
+        1,"[NULL]","[NULL]"
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/prelude/nested_slices_trace.py b/test/trace_processor/diff_tests/stdlib/prelude/nested_slices_trace.py
new file mode 100644
index 0000000..43cc76a
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/prelude/nested_slices_trace.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+from os import sys
+
+import synth_common
+
+from synth_common import ms_to_ns
+
+trace = synth_common.create_trace()
+
+track1_id = 1
+track2_id = 2
+
+trace.add_track_descriptor(track1_id)
+trace.add_track_descriptor(track2_id)
+
+trace.add_track_event_slice("Slice 1", ts=1, dur=10, track=track1_id)
+trace.add_track_event_slice("Slice 2", ts=2, dur=3, track=track1_id)
+trace.add_track_event_slice("Slice 3", ts=6, dur=3, track=track1_id)
+trace.add_track_event_slice("Slice 4", ts=3, dur=1, track=track2_id)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/stdlib/prelude/pprof_functions_tests.py b/test/trace_processor/diff_tests/stdlib/prelude/pprof_functions_tests.py
new file mode 100644
index 0000000..d810c08
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/prelude/pprof_functions_tests.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+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
+
+
+class PreludePprofFunctions(TestSuite):
+
+  def test_stacks(self):
+    return DiffTestBlueprint(
+        trace=DataPath("perf_sample.pb"),
+        query="""
+        SELECT HEX(
+          CAT_STACKS(
+            "A",
+            CAT_STACKS(
+              "B",
+              CAT_STACKS(
+                "C",
+                STACK_FROM_STACK_PROFILE_CALLSITE(5),
+                "D")
+              ),
+            "E",
+            NULL,
+            STACK_FROM_STACK_PROFILE_CALLSITE(14),
+            STACK_FROM_STACK_PROFILE_CALLSITE(NULL),
+            STACK_FROM_STACK_PROFILE_FRAME(4),
+            STACK_FROM_STACK_PROFILE_FRAME(NULL)))
+        """,
+        out=BinaryProto(
+            message_type="perfetto.protos.Stack",
+            contents="""
+              entries {
+                frame_id: 4
+              }
+              entries {
+                callsite_id: 14
+              }
+              entries {
+                name: "E"
+              }
+              entries {
+                name: "D"
+              }
+              entries {
+                callsite_id: 5
+              }
+              entries {
+                name: "C"
+              }
+              entries {
+                name: "B"
+              }
+              entries {
+                name: "A"
+              }
+        """))
+
+  def test_profile_no_functions(self):
+    return DiffTestBlueprint(
+        trace=DataPath("perf_sample_no_functions.pb"),
+        query="""
+        SELECT HEX(
+          EXPERIMENTAL_PROFILE(STACK_FROM_STACK_PROFILE_CALLSITE(callsite_id))
+        )
+        FROM PERF_SAMPLE
+    """,
+        out=BinaryProto(
+            message_type="perfetto.third_party.perftools.profiles.Profile",
+            post_processing=PrintProfileProto,
+            contents="""
+        Sample:
+          Values: 1
+          Stack:
+            (0x7a4167d3f8)
+            (0x783153c8e4)
+            (0x7a4161ef8c)
+            (0x7a42c3d8b0)
+            (0x7a4167d9f4)
+            (0x7a4163bc44)
+            (0x7a4172f330)
+            (0x7a4177a658)
+            (0x7a4162b3a0)
+
+        Sample:
+          Values: 1
+          Stack:
+            (0x7a4167d9f8)
+            (0x7a4163bc44)
+            (0x7a4172f330)
+            (0x7a4177a658)
+            (0x7a4162b3a0)
+        """))
+
+  def test_profile_default_sample_types(self):
+    return DiffTestBlueprint(
+        trace=DataPath("perf_sample.pb"),
+        query="""
+        SELECT HEX(
+          EXPERIMENTAL_PROFILE(
+            CAT_STACKS(
+              "A",
+              STACK_FROM_STACK_PROFILE_CALLSITE(2),
+              "B"
+        )))
+        """,
+        out=BinaryProto(
+            message_type="perfetto.third_party.perftools.profiles.Profile",
+            post_processing=PrintProfileProto,
+            contents="""
+            Sample:
+              Values: 1
+              Stack:
+                B (0x0)
+                perfetto::base::UnixTaskRunner::Run() (0x7a4172f330)
+                perfetto::ServiceMain(int, char**) (0x7a4177a658)
+                __libc_init (0x7a4162b3a0)
+                A (0x0)
+            """))
+
+  def test_profile_with_sample_types(self):
+    return DiffTestBlueprint(
+        trace=DataPath("perf_sample.pb"),
+        query="""
+        SELECT HEX(
+          EXPERIMENTAL_PROFILE(
+            CAT_STACKS("A", "B"), "type", "units", 42))
+        """,
+        out=BinaryProto(
+            message_type="perfetto.third_party.perftools.profiles.Profile",
+            post_processing=PrintProfileProto,
+            contents="""
+            Sample:
+              Values: 42
+                Stack:
+                  B (0x0)
+                  A (0x0)
+            """))
+
+  def test_profile_aggregates_samples(self):
+    return DiffTestBlueprint(
+        trace=DataPath("perf_sample.pb"),
+        query="""
+        WITH samples(stack, value) AS (
+        VALUES
+          (CAT_STACKS("A", "B"), 4),
+          (CAT_STACKS("A", "B"), 8),
+          (CAT_STACKS("A", "B"), 15),
+          (CAT_STACKS("A", "C"), 16),
+          (CAT_STACKS("C", "B"), 23),
+          (CAT_STACKS("C", "B"), 42)
+        )
+        SELECT HEX(
+          EXPERIMENTAL_PROFILE(
+            stack, "type", "units", value))
+        FROM samples
+        """,
+        out=BinaryProto(
+            message_type="perfetto.third_party.perftools.profiles.Profile",
+            post_processing=PrintProfileProto,
+            contents="""
+            Sample:
+              Values: 16
+              Stack:
+                C (0x0)
+                A (0x0)
+
+            Sample:
+              Values: 27
+              Stack:
+                B (0x0)
+                A (0x0)
+
+            Sample:
+              Values: 65
+              Stack:
+                B (0x0)
+                C (0x0)
+            """))
+
+  def test_annotated_callstack(self):
+    return DiffTestBlueprint(
+        trace=DataPath("perf_sample_annotations.pftrace"),
+        query="""
+        SELECT HEX(EXPERIMENTAL_PROFILE(STACK_FROM_STACK_PROFILE_CALLSITE(251, TRUE)))
+        """,
+        out=BinaryProto(
+            message_type="perfetto.third_party.perftools.profiles.Profile",
+            post_processing=PrintProfileProto,
+            contents="""
+            Sample:
+              Values: 1
+              Stack:
+                art::ResolveFieldWithAccessChecks(art::Thread*, art::ClassLinker*, unsigned short, art::ArtMethod*, bool, bool, unsigned long) [common-frame] (0x724da79a74)
+                NterpGetInstanceFieldOffset [common-frame-interp] (0x724da794b0)
+                nterp_get_instance_field_offset [common-frame-interp] (0x724dcfc070)
+                nterp_op_iget_object_slow_path [common-frame-interp] (0x724dcf5884)
+                android.view.ViewRootImpl.notifyDrawStarted [interp] (0x7248f894d2)
+                android.view.ViewRootImpl.performTraversals [aot] (0x71b8d378)
+                android.view.ViewRootImpl.doTraversal [aot] (0x71b93220)
+                android.view.ViewRootImpl$TraversalRunnable.run [aot] (0x71ab0384)
+                android.view.Choreographer.doCallbacks [aot] (0x71a91b6c)
+                android.view.Choreographer.doFrame [aot] (0x71a92550)
+                android.view.Choreographer$FrameDisplayEventReceiver.run [aot] (0x71b26fb0)
+                android.os.Handler.dispatchMessage [aot] (0x71975924)
+                android.os.Looper.loopOnce [aot] (0x71978d6c)
+                android.os.Looper.loop [aot] (0x719788a0)
+                android.app.ActivityThread.main [aot] (0x717454cc)
+                art_quick_invoke_static_stub [common-frame] (0x724db2de00)
+                _jobject* art::InvokeMethod<(art::PointerSize)8>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jobject*, _jobject*, unsigned long) [common-frame] (0x724db545ec)
+                art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*) (.__uniq.165753521025965369065708152063621506277) (0x724db53ad0)
+                art_jni_trampoline [common-frame] (0x6ff5c578)
+                com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run [aot] (0x71c4ab6c)
+                com.android.internal.os.ZygoteInit.main [aot] (0x71c54c7c)
+                art_quick_invoke_static_stub (0x724db2de00)
+                art::JValue art::InvokeWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list) (0x724dc422a8)
+                art::JNI<true>::CallStaticVoidMethodV(_JNIEnv*, _jclass*, _jmethodID*, std::__va_list) (0x724dcc57c8)
+                _JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...) (0x74e1b03ca8)
+                android::AndroidRuntime::start(char const*, android::Vector<android::String8> const&, bool) (0x74e1b0feac)
+                main (0x63da9c354c)
+                __libc_init (0x74ff4a0728)
+            """))
diff --git a/test/trace_processor/diff_tests/stdlib/prelude/slices_tests.py b/test/trace_processor/diff_tests/stdlib/prelude/slices_tests.py
new file mode 100644
index 0000000..793b713
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/prelude/slices_tests.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+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 google.protobuf import text_format
+
+
+class PreludeSlices(TestSuite):
+
+  def test_slice_is_ancestor(self):
+    return DiffTestBlueprint(
+        trace=Path('nested_slices_trace.py'),
+        query="""
+        SELECT
+          s1.name, s2.name, slice_is_ancestor(s1.id, s2.id) AS is_ancestor
+        FROM slice s1
+        JOIN slice s2
+        WHERE s1.name < s2.name
+      """,
+        out=Csv("""
+        "name","name","is_ancestor"
+        "Slice 1","Slice 2",1
+        "Slice 1","Slice 4",0
+        "Slice 1","Slice 3",1
+        "Slice 2","Slice 4",0
+        "Slice 2","Slice 3",0
+        "Slice 3","Slice 4",0
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/prelude/window_functions_tests.py b/test/trace_processor/diff_tests/stdlib/prelude/window_functions_tests.py
new file mode 100644
index 0000000..2840394
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/prelude/window_functions_tests.py
@@ -0,0 +1,220 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+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
+
+
+class PreludeWindowFunctions(TestSuite):
+
+  def test_first_non_null_frame(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        CREATE TABLE TEST(id INTEGER, val INTEGER);
+
+        INSERT INTO TEST
+        VALUES (1, 1), (2, NULL), (3, 3), (4, 4), (5, NULL), (6, NULL), (7, NULL);
+
+        SELECT
+          id,
+          LAST_NON_NULL(val)
+          OVER (ORDER BY id ASC ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS val
+        FROM TEST
+        ORDER BY id ASC;
+        """,
+        out=Csv("""
+        "id","val"
+        1,3
+        2,4
+        3,4
+        4,4
+        5,"[NULL]"
+        6,"[NULL]"
+        7,"[NULL]"
+        """))
+
+  def test_first_non_null_partition(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        CREATE TABLE TEST(id INTEGER, part TEXT, val INTEGER);
+
+        INSERT INTO TEST
+        VALUES
+        (1, 'A', 1),
+        (2, 'A', NULL),
+        (3, 'A', 3),
+        (4, 'B', NULL),
+        (5, 'B', 5),
+        (6, 'B', NULL),
+        (7, 'B', 7);
+
+        SELECT id, LAST_NON_NULL(val) OVER (PARTITION BY part ORDER BY id ASC) AS val
+        FROM TEST
+        ORDER BY id ASC;
+        """,
+        out=Csv("""
+        "id","val"
+        1,1
+        2,1
+        3,3
+        4,"[NULL]"
+        5,5
+        6,5
+        7,7
+        """))
+
+  def test_first_non_null(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+
+        """),
+        query="""
+        CREATE TABLE TEST(id INTEGER, val INTEGER);
+
+        INSERT INTO TEST
+        VALUES (1, 1), (2, NULL), (3, 3), (4, 4), (5, NULL), (6, NULL), (7, NULL);
+
+        SELECT id, LAST_NON_NULL(val) OVER (ORDER BY id ASC) AS val
+        FROM TEST
+        ORDER BY id ASC;
+        """,
+        out=Csv("""
+        "id","val"
+        1,1
+        2,1
+        3,3
+        4,4
+        5,4
+        6,4
+        7,4
+        """))
+
+  def test_layout(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        """),
+        query="""
+        CREATE TABLE TEST(start INTEGER, end INTEGER);
+
+        INSERT INTO TEST
+        VALUES
+        (1, 5),
+        (2, 4),
+        (3, 8),
+        (6, 7),
+        (6, 7),
+        (6, 7);
+
+        WITH custom_slices as (
+          SELECT
+            start as ts,
+            end - start as dur
+          FROM test
+        )
+        SELECT
+          ts,
+          INTERNAL_LAYOUT(ts, dur) over (
+            order by ts
+            rows between unbounded preceding and current row
+          ) as depth
+        FROM custom_slices
+        """,
+        out=Csv("""
+        "ts","depth"
+        1,0
+        2,1
+        3,2
+        6,0
+        6,1
+        6,3
+        """))
+
+  def test_layout_with_instant_events(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        """),
+        query="""
+        CREATE TABLE TEST(start INTEGER, end INTEGER);
+
+        INSERT INTO TEST
+        VALUES
+        (1, 5),
+        (2, 2),
+        (3, 3),
+        (4, 4);
+
+        WITH custom_slices as (
+          SELECT
+            start as ts,
+            end - start as dur
+          FROM test
+        )
+        SELECT
+          ts,
+          INTERNAL_LAYOUT(ts, dur) over (
+            order by ts
+            rows between unbounded preceding and current row
+          ) as depth
+        FROM custom_slices
+        """,
+        out=Csv("""
+        "ts","depth"
+        1,0
+        2,1
+        3,1
+        4,1
+        """))
+
+  def test_layout_with_events_without_end(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        """),
+        query="""
+        CREATE TABLE TEST(ts INTEGER, dur INTEGER);
+
+        INSERT INTO TEST
+        VALUES
+        (1, -1),
+        (2, -1),
+        (3, 5),
+        (4, 1),
+        (5, 1);
+
+        SELECT
+          ts,
+          INTERNAL_LAYOUT(ts, dur) over (
+            order by ts
+            rows between unbounded preceding and current row
+          ) as depth
+        FROM test
+        """,
+        out=Csv("""
+        "ts","depth"
+        1,0
+        2,1
+        3,2
+        4,3
+        5,3
+        """))
diff --git a/test/trace_processor/diff_tests/stdlib/tests.py b/test/trace_processor/diff_tests/stdlib/tests.py
new file mode 100644
index 0000000..5f2ca20
--- /dev/null
+++ b/test/trace_processor/diff_tests/stdlib/tests.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+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 DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+# Smoke tests for the entire stdlib. These tests load a trace and then import the entire stdlib to check that it's well-formed.
+class StdlibSmoke(TestSuite):
+
+  def test_empty_file(self):
+    return DiffTestBlueprint(
+        trace=TextProto(''),
+        query="""
+        INCLUDE PERFETTO MODULE *;
+
+        SELECT 1 as result;
+        """,
+        out=Csv("""
+        "result"
+        1
+        """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/syntax/function_tests.py b/test/trace_processor/diff_tests/syntax/function_tests.py
new file mode 100644
index 0000000..6c656ba
--- /dev/null
+++ b/test/trace_processor/diff_tests/syntax/function_tests.py
@@ -0,0 +1,296 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+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
+
+
+class PerfettoFunction(TestSuite):
+
+  def test_create_function(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO FUNCTION f(x INT) RETURNS INT AS SELECT $x + 1;
+
+        SELECT f(5) as result;
+      """,
+        out=Csv("""
+        "result"
+        6
+      """))
+
+  def test_replace_function(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO FUNCTION f(x INT) RETURNS INT AS SELECT $x + 1;
+        CREATE OR REPLACE PERFETTO FUNCTION f(x INT) RETURNS INT AS SELECT $x + 2;
+
+        SELECT f(5) as result;
+      """,
+        out=Csv("""
+        "result"
+        7
+      """))
+
+  def test_legacy_create_function(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT create_function('f(x INT)', 'INT', 'SELECT $x + 1');
+
+        SELECT f(5) as result;
+      """,
+        out=Csv("""
+        "result"
+        6
+      """))
+
+  def test_legacy_create_function_returns_string(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT create_function('f(x INT)', 'STRING', 'SELECT "value_" || $x');
+
+        SELECT f(5) as result;
+      """,
+        out=Csv("""
+        "result"
+        "value_5"
+      """))
+
+  def test_legacy_create_function_duplicated(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT create_function('f()', 'INT', 'SELECT 1');
+        SELECT create_function('f()', 'INT', 'SELECT 1');
+
+        SELECT f() as result;
+      """,
+        out=Csv("""
+        "result"
+        1
+      """))
+
+  def test_legacy_create_function_recursive(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        -- Compute factorial.
+        SELECT create_function('f(x INT)', 'INT',
+        '
+          SELECT IIF($x = 0, 1, $x * f($x - 1))
+        ');
+
+        SELECT f(5) as result;
+      """,
+        out=Csv("""
+        "result"
+        120
+      """))
+
+  def test_legacy_create_function_recursive_string(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        -- Compute factorial.
+        SELECT create_function('f(x INT)', 'STRING',
+        '
+          SELECT IIF(
+            $x = 0,
+            "",
+            -- 97 is the ASCII code for "a".
+            f($x - 1) || char(96 + $x) || f($x - 1))
+        ');
+
+        SELECT f(4) as result;
+      """,
+        out=Csv("""
+          "result"
+          "abacabadabacaba"
+      """))
+
+  def test_legacy_create_function_recursive_string_memoized(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        -- Compute factorial.
+        SELECT create_function('f(x INT)', 'STRING',
+        '
+          SELECT IIF(
+            $x = 0,
+            "",
+            -- 97 is the ASCII code for "a".
+            f($x - 1) || char(96 + $x) || f($x - 1))
+        ');
+
+        SELECT experimental_memoize('f');
+
+        SELECT f(4) as result;
+      """,
+        out=Csv("""
+          "result"
+          "abacabadabacaba"
+      """))
+
+  def test_legacy_create_function_memoize(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        -- Compute 2^n inefficiently to test memoization.
+        -- If it times out, memoization is not working.
+        SELECT create_function('f(x INT)', 'INT',
+        '
+          SELECT IIF($x = 0, 1, f($x - 1) + f($x - 1))
+        ');
+
+        SELECT EXPERIMENTAL_MEMOIZE('f');
+
+        -- 2^50 is too expensive to compute, but memoization makes it fast.
+        SELECT f(50) as result;
+      """,
+        out=Csv("""
+        "result"
+        1125899906842624
+      """))
+
+  def test_legacy_create_function_memoize_float(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        -- Compute 2^n inefficiently to test memoization.
+        -- If it times out, memoization is not working.
+        SELECT create_function('f(x INT)', 'FLOAT',
+        '
+          SELECT $x + 0.5
+        ');
+
+        SELECT EXPERIMENTAL_MEMOIZE('f');
+
+        SELECT printf("%.1f", f(1)) as result
+        UNION ALL
+        SELECT printf("%.1f", f(1)) as result
+        UNION ALL
+        SELECT printf("%.1f", f(1)) as result
+      """,
+        out=Csv("""
+        "result"
+        "1.5"
+        "1.5"
+        "1.5"
+      """))
+
+  def test_legacy_create_function_memoize_intermittent_memoization(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        -- This function returns NULL for odd numbers and 1 for even numbers.
+        -- As we do not memoize NULL results, we would only memoize the results
+        -- for even numbers.
+        SELECT create_function('f(x INT)', 'INT',
+        '
+          SELECT IIF($x = 0, 1,
+            IIF(f($x - 1) IS NULL, 1, NULL)
+          )
+        ');
+
+        SELECT EXPERIMENTAL_MEMOIZE('f');
+
+        SELECT
+          f(50) as f_50,
+          f(51) as f_51;
+      """,
+        out=Csv("""
+        "f_50","f_51"
+        1,"[NULL]"
+      """))
+
+  def test_legacy_create_function_memoize_subtree_size(self):
+    # Tree:
+    #            1
+    #           / \
+    #          /   \
+    #         /     \
+    #        2       3
+    #       / \     / \
+    #      4   5   6   7
+    #     / \  |   |  | \
+    #    8   9 10 11 12 13
+    #    |   |
+    #   14   15
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO TABLE tree AS
+        WITH data(id, parent_id) as (VALUES
+          (1, NULL),
+          (2, 1),
+          (3, 1),
+          (4, 2),
+          (5, 2),
+          (6, 3),
+          (7, 3),
+          (8, 4),
+          (9, 4),
+          (10, 5),
+          (11, 6),
+          (12, 7),
+          (13, 7),
+          (14, 8),
+          (15, 9)
+        )
+        SELECT * FROM data;
+
+        SELECT create_function('subtree_size(id INT)', 'INT',
+        '
+          SELECT 1 + IFNULL((
+            SELECT
+              SUM(subtree_size(child.id))
+            FROM tree child
+            WHERE child.parent_id = $id
+          ), 0)
+        ');
+
+        SELECT EXPERIMENTAL_MEMOIZE('subtree_size');
+
+        SELECT
+          id, subtree_size(id) as size
+        FROM tree
+        ORDER BY id;
+      """,
+        out=Csv("""
+        "id","size"
+        1,15
+        2,8
+        3,6
+        4,5
+        5,2
+        6,2
+        7,3
+        8,2
+        9,2
+        10,1
+        11,1
+        12,1
+        13,1
+        14,1
+        15,1
+      """))
\ No newline at end of file
diff --git a/test/trace_processor/diff_tests/syntax/functions/tests.py b/test/trace_processor/diff_tests/syntax/functions/tests.py
deleted file mode 100644
index f613fb5..0000000
--- a/test/trace_processor/diff_tests/syntax/functions/tests.py
+++ /dev/null
@@ -1,879 +0,0 @@
-#!/usr/bin/env python3
-# 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 a
-#
-#      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.
-
-from python.generators.diff_tests.testing import Path, DataPath, Metric
-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
-
-
-class Functions(TestSuite):
-
-  def test_create_function(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        SELECT create_function('f(x INT)', 'INT', 'SELECT $x + 1');
-
-        SELECT f(5) as result;
-      """,
-        out=Csv("""
-        "result"
-        6
-      """))
-
-  def test_create_function_returns_string(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        SELECT create_function('f(x INT)', 'STRING', 'SELECT "value_" || $x');
-
-        SELECT f(5) as result;
-      """,
-        out=Csv("""
-        "result"
-        "value_5"
-      """))
-
-  def test_create_function_duplicated(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        SELECT create_function('f()', 'INT', 'SELECT 1');
-        SELECT create_function('f()', 'INT', 'SELECT 1');
-
-        SELECT f() as result;
-      """,
-        out=Csv("""
-        "result"
-        1
-      """))
-
-  def test_create_function_recursive(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        -- Compute factorial.
-        SELECT create_function('f(x INT)', 'INT',
-        '
-          SELECT IIF($x = 0, 1, $x * f($x - 1))
-        ');
-
-        SELECT f(5) as result;
-      """,
-        out=Csv("""
-        "result"
-        120
-      """))
-
-  def test_create_function_recursive_string(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        -- Compute factorial.
-        SELECT create_function('f(x INT)', 'STRING',
-        '
-          SELECT IIF(
-            $x = 0,
-            "",
-            -- 97 is the ASCII code for "a".
-            f($x - 1) || char(96 + $x) || f($x - 1))
-        ');
-
-        SELECT f(4) as result;
-      """,
-        out=Csv("""
-          "result"
-          "abacabadabacaba"
-      """))
-
-  def test_create_function_recursive_string_memoized(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        -- Compute factorial.
-        SELECT create_function('f(x INT)', 'STRING',
-        '
-          SELECT IIF(
-            $x = 0,
-            "",
-            -- 97 is the ASCII code for "a".
-            f($x - 1) || char(96 + $x) || f($x - 1))
-        ');
-
-        SELECT experimental_memoize('f');
-
-        SELECT f(4) as result;
-      """,
-        out=Csv("""
-          "result"
-          "abacabadabacaba"
-      """))
-
-  def test_create_function_memoize(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        -- Compute 2^n inefficiently to test memoization.
-        -- If it times out, memoization is not working.
-        SELECT create_function('f(x INT)', 'INT',
-        '
-          SELECT IIF($x = 0, 1, f($x - 1) + f($x - 1))
-        ');
-
-        SELECT EXPERIMENTAL_MEMOIZE('f');
-
-        -- 2^50 is too expensive to compute, but memoization makes it fast.
-        SELECT f(50) as result;
-      """,
-        out=Csv("""
-        "result"
-        1125899906842624
-      """))
-
-  def test_create_function_memoize_float(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        -- Compute 2^n inefficiently to test memoization.
-        -- If it times out, memoization is not working.
-        SELECT create_function('f(x INT)', 'FLOAT',
-        '
-          SELECT $x + 0.5
-        ');
-
-        SELECT EXPERIMENTAL_MEMOIZE('f');
-
-        SELECT printf("%.1f", f(1)) as result
-        UNION ALL
-        SELECT printf("%.1f", f(1)) as result
-        UNION ALL
-        SELECT printf("%.1f", f(1)) as result
-      """,
-        out=Csv("""
-        "result"
-        "1.5"
-        "1.5"
-        "1.5"
-      """))
-
-  def test_create_function_memoize_intermittent_memoization(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        -- This function returns NULL for odd numbers and 1 for even numbers.
-        -- As we do not memoize NULL results, we would only memoize the results
-        -- for even numbers.
-        SELECT create_function('f(x INT)', 'INT',
-        '
-          SELECT IIF($x = 0, 1,
-            IIF(f($x - 1) IS NULL, 1, NULL)
-          )
-        ');
-
-        SELECT EXPERIMENTAL_MEMOIZE('f');
-
-        SELECT
-          f(50) as f_50,
-          f(51) as f_51;
-      """,
-        out=Csv("""
-        "f_50","f_51"
-        1,"[NULL]"
-      """))
-
-  def test_create_function_memoize_subtree_size(self):
-    # Tree:
-    #            1
-    #           / \
-    #          /   \
-    #         /     \
-    #        2       3
-    #       / \     / \
-    #      4   5   6   7
-    #     / \  |   |  | \
-    #    8   9 10 11 12 13
-    #    |   |
-    #   14   15
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        CREATE PERFETTO TABLE tree AS
-        WITH data(id, parent_id) as (VALUES
-          (1, NULL),
-          (2, 1),
-          (3, 1),
-          (4, 2),
-          (5, 2),
-          (6, 3),
-          (7, 3),
-          (8, 4),
-          (9, 4),
-          (10, 5),
-          (11, 6),
-          (12, 7),
-          (13, 7),
-          (14, 8),
-          (15, 9)
-        )
-        SELECT * FROM data;
-
-        SELECT create_function('subtree_size(id INT)', 'INT',
-        '
-          SELECT 1 + IFNULL((
-            SELECT
-              SUM(subtree_size(child.id))
-            FROM tree child
-            WHERE child.parent_id = $id
-          ), 0)
-        ');
-
-        SELECT EXPERIMENTAL_MEMOIZE('subtree_size');
-
-        SELECT
-          id, subtree_size(id) as size
-        FROM tree
-        ORDER BY id;
-      """,
-        out=Csv("""
-        "id","size"
-        1,15
-        2,8
-        3,6
-        4,5
-        5,2
-        6,2
-        7,3
-        8,2
-        9,2
-        10,1
-        11,1
-        12,1
-        13,1
-        14,1
-        15,1
-      """))
-
-  def test_create_view_function(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        SELECT create_view_function('f(x INT)', 'result INT', 'SELECT $x + 1 as result');
-
-        SELECT * FROM f(5);
-      """,
-        out=Csv("""
-        "result"
-        6
-      """))
-
-  def test_first_non_null_frame(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        CREATE TABLE TEST(id INTEGER, val INTEGER);
-
-        INSERT INTO TEST
-        VALUES (1, 1), (2, NULL), (3, 3), (4, 4), (5, NULL), (6, NULL), (7, NULL);
-
-        SELECT
-          id,
-          LAST_NON_NULL(val)
-          OVER (ORDER BY id ASC ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING) AS val
-        FROM TEST
-        ORDER BY id ASC;
-        """,
-        out=Csv("""
-        "id","val"
-        1,3
-        2,4
-        3,4
-        4,4
-        5,"[NULL]"
-        6,"[NULL]"
-        7,"[NULL]"
-        """))
-
-  def test_first_non_null_partition(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        CREATE TABLE TEST(id INTEGER, part TEXT, val INTEGER);
-
-        INSERT INTO TEST
-        VALUES
-        (1, 'A', 1),
-        (2, 'A', NULL),
-        (3, 'A', 3),
-        (4, 'B', NULL),
-        (5, 'B', 5),
-        (6, 'B', NULL),
-        (7, 'B', 7);
-
-        SELECT id, LAST_NON_NULL(val) OVER (PARTITION BY part ORDER BY id ASC) AS val
-        FROM TEST
-        ORDER BY id ASC;
-        """,
-        out=Csv("""
-        "id","val"
-        1,1
-        2,1
-        3,3
-        4,"[NULL]"
-        5,5
-        6,5
-        7,7
-        """))
-
-  def test_first_non_null(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        CREATE TABLE TEST(id INTEGER, val INTEGER);
-
-        INSERT INTO TEST
-        VALUES (1, 1), (2, NULL), (3, 3), (4, 4), (5, NULL), (6, NULL), (7, NULL);
-
-        SELECT id, LAST_NON_NULL(val) OVER (ORDER BY id ASC) AS val
-        FROM TEST
-        ORDER BY id ASC;
-        """,
-        out=Csv("""
-        "id","val"
-        1,1
-        2,1
-        3,3
-        4,4
-        5,4
-        6,4
-        7,4
-        """))
-
-  def test_spans_overlapping_dur_intersect_edge(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        INCLUDE PERFETTO MODULE common.timestamps;
-        SELECT SPANS_OVERLAPPING_DUR(0, 2, 1, 2) AS dur
-        """,
-        out=Csv("""
-        "dur"
-        1
-        """))
-
-  def test_spans_overlapping_dur_intersect_edge_reversed(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        INCLUDE PERFETTO MODULE common.timestamps;
-        SELECT SPANS_OVERLAPPING_DUR(1, 2, 0, 2) AS dur
-        """,
-        out=Csv("""
-        "dur"
-        1
-        """))
-
-  def test_spans_overlapping_dur_intersect_all(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        INCLUDE PERFETTO MODULE common.timestamps;
-        SELECT SPANS_OVERLAPPING_DUR(0, 3, 1, 1) AS dur
-        """,
-        out=Csv("""
-        "dur"
-        1
-        """))
-
-  def test_spans_overlapping_dur_intersect_all_reversed(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        INCLUDE PERFETTO MODULE common.timestamps;
-        SELECT SPANS_OVERLAPPING_DUR(1, 1, 0, 3) AS dur
-        """,
-        out=Csv("""
-        "dur"
-        1
-        """))
-
-  def test_spans_overlapping_dur_no_intersect(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        INCLUDE PERFETTO MODULE common.timestamps;
-        SELECT SPANS_OVERLAPPING_DUR(0, 1, 2, 1) AS dur
-        """,
-        out=Csv("""
-        "dur"
-        0
-        """))
-
-  def test_spans_overlapping_dur_no_intersect_reversed(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        INCLUDE PERFETTO MODULE common.timestamps;
-        SELECT SPANS_OVERLAPPING_DUR(2, 1, 0, 1) AS dur
-        """,
-        out=Csv("""
-        "dur"
-        0
-        """))
-
-  def test_spans_overlapping_dur_negative_dur(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        INCLUDE PERFETTO MODULE common.timestamps;
-        SELECT SPANS_OVERLAPPING_DUR(0, -1, 0, 1) AS dur
-        """,
-        out=Csv("""
-        "dur"
-        0
-        """))
-
-  def test_spans_overlapping_dur_negative_dur_reversed(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-
-        """),
-        query="""
-        INCLUDE PERFETTO MODULE common.timestamps;
-        SELECT SPANS_OVERLAPPING_DUR(0, 1, 0, -1) AS dur
-        """,
-        out=Csv("""
-        "dur"
-        0
-        """))
-
-  def test_stacks(self):
-    return DiffTestBlueprint(
-        trace=DataPath("perf_sample.pb"),
-        query="""
-        SELECT HEX(
-          CAT_STACKS(
-            "A",
-            CAT_STACKS(
-              "B",
-              CAT_STACKS(
-                "C",
-                STACK_FROM_STACK_PROFILE_CALLSITE(5),
-                "D")
-              ),
-            "E",
-            NULL,
-            STACK_FROM_STACK_PROFILE_CALLSITE(14),
-            STACK_FROM_STACK_PROFILE_CALLSITE(NULL),
-            STACK_FROM_STACK_PROFILE_FRAME(4),
-            STACK_FROM_STACK_PROFILE_FRAME(NULL)))
-        """,
-        out=BinaryProto(
-            message_type="perfetto.protos.Stack",
-            contents="""
-              entries {
-                frame_id: 4
-              }
-              entries {
-                callsite_id: 14
-              }
-              entries {
-                name: "E"
-              }
-              entries {
-                name: "D"
-              }
-              entries {
-                callsite_id: 5
-              }
-              entries {
-                name: "C"
-              }
-              entries {
-                name: "B"
-              }
-              entries {
-                name: "A"
-              }
-        """))
-
-  def test_profile_no_functions(self):
-    return DiffTestBlueprint(
-        trace=DataPath("perf_sample_no_functions.pb"),
-        query="""
-        SELECT HEX(
-          EXPERIMENTAL_PROFILE(STACK_FROM_STACK_PROFILE_CALLSITE(callsite_id))
-        )
-        FROM PERF_SAMPLE
-    """,
-        out=BinaryProto(
-            message_type="perfetto.third_party.perftools.profiles.Profile",
-            post_processing=PrintProfileProto,
-            contents="""
-        Sample:
-          Values: 1
-          Stack:
-            (0x7a4167d3f8)
-            (0x783153c8e4)
-            (0x7a4161ef8c)
-            (0x7a42c3d8b0)
-            (0x7a4167d9f4)
-            (0x7a4163bc44)
-            (0x7a4172f330)
-            (0x7a4177a658)
-            (0x7a4162b3a0)
-
-        Sample:
-          Values: 1
-          Stack:
-            (0x7a4167d9f8)
-            (0x7a4163bc44)
-            (0x7a4172f330)
-            (0x7a4177a658)
-            (0x7a4162b3a0)
-        """))
-
-  def test_profile_default_sample_types(self):
-    return DiffTestBlueprint(
-        trace=DataPath("perf_sample.pb"),
-        query="""
-        SELECT HEX(
-          EXPERIMENTAL_PROFILE(
-            CAT_STACKS(
-              "A",
-              STACK_FROM_STACK_PROFILE_CALLSITE(2),
-              "B"
-        )))
-        """,
-        out=BinaryProto(
-            message_type="perfetto.third_party.perftools.profiles.Profile",
-            post_processing=PrintProfileProto,
-            contents="""
-            Sample:
-              Values: 1
-              Stack:
-                B (0x0)
-                perfetto::base::UnixTaskRunner::Run() (0x7a4172f330)
-                perfetto::ServiceMain(int, char**) (0x7a4177a658)
-                __libc_init (0x7a4162b3a0)
-                A (0x0)
-            """))
-
-  def test_profile_with_sample_types(self):
-    return DiffTestBlueprint(
-        trace=DataPath("perf_sample.pb"),
-        query="""
-        SELECT HEX(
-          EXPERIMENTAL_PROFILE(
-            CAT_STACKS("A", "B"), "type", "units", 42))
-        """,
-        out=BinaryProto(
-            message_type="perfetto.third_party.perftools.profiles.Profile",
-            post_processing=PrintProfileProto,
-            contents="""
-            Sample:
-              Values: 42
-                Stack:
-                  B (0x0)
-                  A (0x0)
-            """))
-
-  def test_profile_aggregates_samples(self):
-    return DiffTestBlueprint(
-        trace=DataPath("perf_sample.pb"),
-        query="""
-        WITH samples(stack, value) AS (
-        VALUES
-          (CAT_STACKS("A", "B"), 4),
-          (CAT_STACKS("A", "B"), 8),
-          (CAT_STACKS("A", "B"), 15),
-          (CAT_STACKS("A", "C"), 16),
-          (CAT_STACKS("C", "B"), 23),
-          (CAT_STACKS("C", "B"), 42)
-        )
-        SELECT HEX(
-          EXPERIMENTAL_PROFILE(
-            stack, "type", "units", value))
-        FROM samples
-        """,
-        out=BinaryProto(
-            message_type="perfetto.third_party.perftools.profiles.Profile",
-            post_processing=PrintProfileProto,
-            contents="""
-            Sample:
-              Values: 16
-              Stack:
-                C (0x0)
-                A (0x0)
-
-            Sample:
-              Values: 27
-              Stack:
-                B (0x0)
-                A (0x0)
-
-            Sample:
-              Values: 65
-              Stack:
-                B (0x0)
-                C (0x0)
-            """))
-
-  def test_annotated_callstack(self):
-    return DiffTestBlueprint(
-        trace=DataPath("perf_sample_annotations.pftrace"),
-        query="""
-        SELECT HEX(EXPERIMENTAL_PROFILE(STACK_FROM_STACK_PROFILE_CALLSITE(251, TRUE)))
-        """,
-        out=BinaryProto(
-            message_type="perfetto.third_party.perftools.profiles.Profile",
-            post_processing=PrintProfileProto,
-            contents="""
-            Sample:
-              Values: 1
-              Stack:
-                art::ResolveFieldWithAccessChecks(art::Thread*, art::ClassLinker*, unsigned short, art::ArtMethod*, bool, bool, unsigned long) [common-frame] (0x724da79a74)
-                NterpGetInstanceFieldOffset [common-frame-interp] (0x724da794b0)
-                nterp_get_instance_field_offset [common-frame-interp] (0x724dcfc070)
-                nterp_op_iget_object_slow_path [common-frame-interp] (0x724dcf5884)
-                android.view.ViewRootImpl.notifyDrawStarted [interp] (0x7248f894d2)
-                android.view.ViewRootImpl.performTraversals [aot] (0x71b8d378)
-                android.view.ViewRootImpl.doTraversal [aot] (0x71b93220)
-                android.view.ViewRootImpl$TraversalRunnable.run [aot] (0x71ab0384)
-                android.view.Choreographer.doCallbacks [aot] (0x71a91b6c)
-                android.view.Choreographer.doFrame [aot] (0x71a92550)
-                android.view.Choreographer$FrameDisplayEventReceiver.run [aot] (0x71b26fb0)
-                android.os.Handler.dispatchMessage [aot] (0x71975924)
-                android.os.Looper.loopOnce [aot] (0x71978d6c)
-                android.os.Looper.loop [aot] (0x719788a0)
-                android.app.ActivityThread.main [aot] (0x717454cc)
-                art_quick_invoke_static_stub [common-frame] (0x724db2de00)
-                _jobject* art::InvokeMethod<(art::PointerSize)8>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jobject*, _jobject*, unsigned long) [common-frame] (0x724db545ec)
-                art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*) (.__uniq.165753521025965369065708152063621506277) (0x724db53ad0)
-                art_jni_trampoline [common-frame] (0x6ff5c578)
-                com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run [aot] (0x71c4ab6c)
-                com.android.internal.os.ZygoteInit.main [aot] (0x71c54c7c)
-                art_quick_invoke_static_stub (0x724db2de00)
-                art::JValue art::InvokeWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list) (0x724dc422a8)
-                art::JNI<true>::CallStaticVoidMethodV(_JNIEnv*, _jclass*, _jmethodID*, std::__va_list) (0x724dcc57c8)
-                _JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...) (0x74e1b03ca8)
-                android::AndroidRuntime::start(char const*, android::Vector<android::String8> const&, bool) (0x74e1b0feac)
-                main (0x63da9c354c)
-                __libc_init (0x74ff4a0728)
-            """))
-
-  def test_layout(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        """),
-        query="""
-        CREATE TABLE TEST(start INTEGER, end INTEGER);
-
-        INSERT INTO TEST
-        VALUES
-        (1, 5),
-        (2, 4),
-        (3, 8),
-        (6, 7),
-        (6, 7),
-        (6, 7);
-
-        WITH custom_slices as (
-          SELECT
-            start as ts,
-            end - start as dur
-          FROM test
-        )
-        SELECT
-          ts,
-          INTERNAL_LAYOUT(ts, dur) over (
-            order by ts
-            rows between unbounded preceding and current row
-          ) as depth
-        FROM custom_slices
-        """,
-        out=Csv("""
-        "ts","depth"
-        1,0
-        2,1
-        3,2
-        6,0
-        6,1
-        6,3
-        """))
-
-  def test_layout_with_instant_events(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        """),
-        query="""
-        CREATE TABLE TEST(start INTEGER, end INTEGER);
-
-        INSERT INTO TEST
-        VALUES
-        (1, 5),
-        (2, 2),
-        (3, 3),
-        (4, 4);
-
-        WITH custom_slices as (
-          SELECT
-            start as ts,
-            end - start as dur
-          FROM test
-        )
-        SELECT
-          ts,
-          INTERNAL_LAYOUT(ts, dur) over (
-            order by ts
-            rows between unbounded preceding and current row
-          ) as depth
-        FROM custom_slices
-        """,
-        out=Csv("""
-        "ts","depth"
-        1,0
-        2,1
-        3,1
-        4,1
-        """))
-
-  def test_layout_with_events_without_end(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r"""
-        """),
-        query="""
-        CREATE TABLE TEST(ts INTEGER, dur INTEGER);
-
-        INSERT INTO TEST
-        VALUES
-        (1, -1),
-        (2, -1),
-        (3, 5),
-        (4, 1),
-        (5, 1);
-
-        SELECT
-          ts,
-          INTERNAL_LAYOUT(ts, dur) over (
-            order by ts
-            rows between unbounded preceding and current row
-          ) as depth
-        FROM test
-        """,
-        out=Csv("""
-        "ts","depth"
-        1,0
-        2,1
-        3,2
-        4,3
-        5,3
-        """))
-
-  def test_math_ln_function(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        SELECT
-          CAST(LN(1) * 1000 AS INTEGER) AS valid,
-          LN("as") AS invalid_str,
-          LN(NULL) AS invalid_null
-        """,
-        out=Csv("""
-        "valid","invalid_str","invalid_null"
-        0,"[NULL]","[NULL]"
-        """))
-
-  def test_math_exp_function(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        SELECT
-          CAST(EXP(1) * 1000 AS INTEGER) AS valid,
-          EXP("asd") AS invalid_str,
-          EXP(NULL) AS invalid_null
-        """,
-        out=Csv("""
-        "valid","invalid_str","invalid_null"
-        2718,"[NULL]","[NULL]"
-        """))
-
-  def test_math_sqrt_function(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        SELECT
-          CAST(SQRT(4) AS INTEGER) AS valid,
-          SQRT("asd") AS invalid_str,
-          SQRT(NULL) AS invalid_null
-        """,
-        out=Csv("""
-        "valid","invalid_str","invalid_null"
-        2,"[NULL]","[NULL]"
-        """))
-
-  def test_math_functions(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-        SELECT
-          CAST(SQRT(EXP(LN(1))) AS INTEGER) AS valid,
-          SQRT(EXP(LN("asd"))) AS invalid_str,
-          SQRT(EXP(LN(NULL))) AS invalid_null
-        """,
-        out=Csv("""
-        "valid","invalid_str","invalid_null"
-        1,"[NULL]","[NULL]"
-        """))
-
-  def test_table_function_drop_partial(self):
-    return DiffTestBlueprint(
-        trace=TextProto(""),
-        query="""
-          CREATE TABLE bar AS SELECT 1;
-
-          CREATE OR REPLACE PERFETTO FUNCTION foo()
-          RETURNS TABLE(x INT) AS
-          SELECT 1 AS x
-          UNION
-          SELECT * FROM bar;
-
-          CREATE TABLE res AS SELECT * FROM foo() LIMIT 1;
-
-          DROP TABLE bar;
-        """,
-        out=Csv(""))
diff --git a/test/trace_processor/diff_tests/syntax/perfetto_sql/tests.py b/test/trace_processor/diff_tests/syntax/include_tests.py
similarity index 74%
rename from test/trace_processor/diff_tests/syntax/perfetto_sql/tests.py
rename to test/trace_processor/diff_tests/syntax/include_tests.py
index d5e37b3..6c4d88a 100644
--- a/test/trace_processor/diff_tests/syntax/perfetto_sql/tests.py
+++ b/test/trace_processor/diff_tests/syntax/include_tests.py
@@ -19,23 +19,7 @@
 from python.generators.diff_tests.testing import TestSuite
 
 
-class PerfettoSql(TestSuite):
-
-  def test_create_perfetto_table_double_metric_run(self):
-    return DiffTestBlueprint(
-        trace=TextProto(r''),
-        query="""
-        SELECT RUN_METRIC('android/cpu_info.sql');
-        SELECT RUN_METRIC('android/cpu_info.sql');
-
-        SELECT * FROM cluster_core_type;
-        """,
-        out=Csv("""
-        "cluster","core_type"
-        0,"little"
-        1,"big"
-        2,"bigger"
-        """))
+class PerfettoInclude(TestSuite):
 
   def test_import(self):
     return DiffTestBlueprint(
@@ -157,31 +141,3 @@
         "TRACE_START()"
         1000
         """))
-
-  def test_macro(self):
-    return DiffTestBlueprint(
-        trace=TextProto(''),
-        query='''
-        CREATE PERFETTO MACRO foo(a Expr,b Expr) RETURNS TableOrSubquery AS
-        SELECT $a - $b;
-        SELECT (foo!(123, 100)) as res;
-        ''',
-        out=Csv("""
-        "res"
-        23
-        """))
-
-  def test_nested_macro(self):
-    return DiffTestBlueprint(
-        trace=TextProto(''),
-        query='''
-        CREATE PERFETTO MACRO foo(a Expr) returns Expr AS $a;
-        CREATE PERFETTO MACRO bar(a Expr) returns Expr AS (SELECT $a);
-        CREATE PERFETTO MACRO baz(a Expr,b Expr) returns TableOrSubquery AS
-        SELECT bar!(foo!(123)) - $b as res;
-        baz!(123, 100);
-        ''',
-        out=Csv("""
-        "res"
-        23
-        """))
diff --git a/test/trace_processor/diff_tests/syntax/macro_tests.py b/test/trace_processor/diff_tests/syntax/macro_tests.py
new file mode 100644
index 0000000..f34aaa0
--- /dev/null
+++ b/test/trace_processor/diff_tests/syntax/macro_tests.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+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 DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class PerfettoMacro(TestSuite):
+
+  def test_macro(self):
+    return DiffTestBlueprint(
+        trace=TextProto(''),
+        query='''
+        CREATE PERFETTO MACRO foo(a Expr,b Expr) RETURNS TableOrSubquery AS
+        SELECT $a - $b;
+        SELECT (foo!(123, 100)) as res;
+        ''',
+        out=Csv("""
+        "res"
+        23
+        """))
+
+  def test_nested_macro(self):
+    return DiffTestBlueprint(
+        trace=TextProto(''),
+        query='''
+        CREATE PERFETTO MACRO foo(a Expr) returns Expr AS $a;
+        CREATE PERFETTO MACRO bar(a Expr) returns Expr AS (SELECT $a);
+        CREATE PERFETTO MACRO baz(a Expr,b Expr) returns TableOrSubquery AS
+        SELECT bar!(foo!(123)) - $b as res;
+        baz!(123, 100);
+        ''',
+        out=Csv("""
+        "res"
+        23
+        """))
+
+  def test_replace_macro(self):
+    return DiffTestBlueprint(
+        trace=TextProto(''),
+        query='''
+        CREATE PERFETTO MACRO foo() RETURNS Expr AS 1;
+        CREATE OR REPLACE PERFETTO MACRO foo() RETURNS Expr AS 2;
+
+        SELECT foo!() as res;
+        ''',
+        out=Csv("""
+        "res"
+        2
+        """))
diff --git a/test/trace_processor/diff_tests/syntax/table_function_tests.py b/test/trace_processor/diff_tests/syntax/table_function_tests.py
new file mode 100644
index 0000000..c2ccad7
--- /dev/null
+++ b/test/trace_processor/diff_tests/syntax/table_function_tests.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+from python.generators.diff_tests.testing import Path, DataPath, Metric
+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
+
+
+class PerfettoTableFunction(TestSuite):
+
+  def test_create_table_function(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO FUNCTION f(x INT) RETURNS TABLE(y INT) AS SELECT $x + 1 as y;
+
+        SELECT * FROM f(5);
+      """,
+        out=Csv("""
+        "y"
+        6
+      """))
+
+  def test_replace_table_function(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        CREATE PERFETTO FUNCTION f(x INT) RETURNS TABLE(y INT) AS SELECT $x + 1 as y;
+        CREATE OR REPLACE PERFETTO FUNCTION f(x INT) RETURNS TABLE(y INT) AS SELECT $x + 2 as y;
+
+        SELECT * FROM f(5);
+      """,
+        out=Csv("""
+        "y"
+        7
+      """))
+
+  def test_legacy_create_view_function(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT create_view_function('f(x INT)', 'result INT', 'SELECT $x + 1 as result');
+
+        SELECT * FROM f(5);
+      """,
+        out=Csv("""
+        "result"
+        6
+      """))
+
+  def test_legacy_table_function_drop_partial(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+          CREATE TABLE bar AS SELECT 1;
+
+          CREATE OR REPLACE PERFETTO FUNCTION foo()
+          RETURNS TABLE(x INT) AS
+          SELECT 1 AS x
+          UNION
+          SELECT * FROM bar;
+
+          CREATE TABLE res AS SELECT * FROM foo() LIMIT 1;
+
+          DROP TABLE bar;
+        """,
+        out=Csv(""))
diff --git a/test/trace_processor/diff_tests/syntax/table_tests.py b/test/trace_processor/diff_tests/syntax/table_tests.py
new file mode 100644
index 0000000..f675760
--- /dev/null
+++ b/test/trace_processor/diff_tests/syntax/table_tests.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+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 DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class PerfettoTable(TestSuite):
+
+  def test_create_table(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r''),
+        query="""
+        CREATE PERFETTO TABLE foo AS SELECT 42 as a;
+
+        SELECT * FROM foo;
+        """,
+        out=Csv("""
+        "a"
+        42
+        """))
+
+  def test_replace_table(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r''),
+        query="""
+        CREATE PERFETTO TABLE foo AS SELECT 42 as a;
+        CREATE OR REPLACE PERFETTO TABLE foo AS SELECT 43 as a;
+
+        SELECT * FROM foo;
+        """,
+        out=Csv("""
+        "a"
+        43
+        """))
+
+  def test_create_perfetto_table_double_metric_run(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r''),
+        query="""
+        SELECT RUN_METRIC('android/cpu_info.sql');
+        SELECT RUN_METRIC('android/cpu_info.sql');
+
+        SELECT * FROM cluster_core_type;
+        """,
+        out=Csv("""
+        "cluster","core_type"
+        0,"little"
+        1,"big"
+        2,"bigger"
+        """))
diff --git a/test/trace_processor/diff_tests/syntax/view_tests.py b/test/trace_processor/diff_tests/syntax/view_tests.py
new file mode 100644
index 0000000..01b4d67
--- /dev/null
+++ b/test/trace_processor/diff_tests/syntax/view_tests.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# 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 a
+#
+#      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.
+
+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 DiffTestBlueprint
+from python.generators.diff_tests.testing import TestSuite
+
+
+class PerfettoView(TestSuite):
+
+  def test_create_view(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r''),
+        query="""
+        CREATE PERFETTO VIEW foo AS SELECT 42 as a;
+
+        SELECT * FROM foo;
+        """,
+        out=Csv("""
+        "a"
+        42
+        """))
+
+  def test_replace_view(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r''),
+        query="""
+        CREATE PERFETTO VIEW Foo AS SELECT 42 as a;
+        CREATE OR REPLACE PERFETTO VIEW Foo AS SELECT 43 as a;
+
+        SELECT * FROM foo;
+        """,
+        out=Csv("""
+        "a"
+        43
+        """))
diff --git a/tools/check_sql_metrics.py b/tools/check_sql_metrics.py
index d1e937e..b577b43 100755
--- a/tools/check_sql_metrics.py
+++ b/tools/check_sql_metrics.py
@@ -34,24 +34,21 @@
 from python.generators.sql_processing.utils import DROP_TABLE_VIEW_PATTERN
 from python.generators.sql_processing.utils import CREATE_TABLE_VIEW_PATTERN
 
+# Allowlist path are relative to the metrics root.
 CREATE_TABLE_ALLOWLIST = {
-    ('/src/trace_processor/metrics/sql/android'
+    ('/android'
      '/android_blocking_calls_cuj_metric.sql'): [
         'android_cujs', 'relevant_binder_calls_with_names',
         'android_blocking_calls_cuj_calls'
     ],
-    '/src/trace_processor/metrics/sql/android/jank/cujs.sql': [
-        'android_jank_cuj'
-    ],
-    '/src/trace_processor/metrics/sql/chrome/gesture_flow_event.sql': [
+    '/android/jank/cujs.sql': ['android_jank_cuj'],
+    '/chrome/gesture_flow_event.sql': [
         '{{prefix}}_latency_info_flow_step_filtered'
     ],
-    '/src/trace_processor/metrics/sql/chrome/gesture_jank.sql': [
+    '/chrome/gesture_jank.sql': [
         '{{prefix}}_jank_maybe_null_prev_and_next_without_precompute'
     ],
-    '/src/trace_processor/metrics/sql/experimental/frame_times.sql': [
-        'DisplayCompositorPresentationEvents'
-    ]
+    '/experimental/frame_times.sql': ['DisplayCompositorPresentationEvents']
 }
 
 
@@ -71,7 +68,7 @@
   return res
 
 
-def check(path: str) -> List[str]:
+def check(path: str, metrics_sources: str) -> List[str]:
   with open(path) as f:
     sql = f.read()
 
@@ -82,6 +79,7 @@
       sql, DROP_TABLE_VIEW_PATTERN)
   errors = check_banned_create_table_as(sql,
                                         path.split(ROOT_DIR)[1],
+                                        metrics_sources.split(ROOT_DIR)[1],
                                         CREATE_TABLE_ALLOWLIST)
   errors += check_banned_create_view_as(sql, path.split(ROOT_DIR)[1])
   for name, [line, type] in create_table_view_dir.items():
@@ -110,7 +108,7 @@
     for f in files:
       path = os.path.join(root, f)
       if path.endswith('.sql'):
-        errors += check(path)
+        errors += check(path, metrics_sources)
 
   if errors:
     sys.stderr.write("\n".join(errors))
diff --git a/tools/check_sql_modules.py b/tools/check_sql_modules.py
index c697ac3..1cacd51 100755
--- a/tools/check_sql_modules.py
+++ b/tools/check_sql_modules.py
@@ -30,29 +30,29 @@
 from python.generators.sql_processing.utils import check_banned_create_table_as
 from python.generators.sql_processing.utils import check_banned_create_view_as
 from python.generators.sql_processing.utils import check_banned_words
+from python.generators.sql_processing.utils import check_banned_include_all
 
+# Allowlist path are relative to the stdlib root.
 CREATE_TABLE_ALLOWLIST = {
-    '/src/trace_processor/perfetto_sql/stdlib/android/binder.sql': [
+    '/android/binder.sql': [
         'internal_oom_score', 'internal_async_binder_reply',
         'internal_binder_async_txn_raw'
     ],
-    '/src/trace_processor/perfetto_sql/stdlib/android/monitor_contention.sql': [
+    '/android/monitor_contention.sql': [
         'internal_isolated', 'android_monitor_contention_chain',
         'android_monitor_contention'
     ],
-    '/src/trace_processor/perfetto_sql/stdlib/chrome/tasks.sql': [
+    '/chrome/tasks.sql': [
         'internal_chrome_mojo_slices', 'internal_chrome_java_views',
         'internal_chrome_scheduler_tasks', 'internal_chrome_tasks'
     ],
-    ('/src/trace_processor/perfetto_sql/stdlib/experimental/'
+    ('/experimental/'
      'thread_executing_span.sql'): [
         'internal_wakeup', 'experimental_thread_executing_span_graph',
         'internal_critical_path', 'internal_wakeup_graph',
         'experimental_thread_executing_span_graph'
     ],
-    '/src/trace_processor/perfetto_sql/stdlib/experimental/flat_slices.sql': [
-        'experimental_slice_flattened'
-    ]
+    '/experimental/flat_slices.sql': ['experimental_slice_flattened']
 }
 
 
@@ -110,13 +110,18 @@
       if 'RUN_METRIC' in line:
         errors.append(f"RUN_METRIC is banned in standard library.\n"
                       f"Offending file: {path}\n")
+      if 'insert into' in line.casefold():
+        errors.append(f"INSERT INTO table is not allowed in standard library.\n"
+                      f"Offending file: {path}\n")
 
     errors += parsed.errors
     errors += check_banned_words(sql, path)
-    errors += check_banned_create_table_as(sql,
-                                           path.split(ROOT_DIR)[1],
-                                           CREATE_TABLE_ALLOWLIST)
+    errors += check_banned_create_table_as(
+        sql,
+        path.split(ROOT_DIR)[1],
+        args.stdlib_sources.split(ROOT_DIR)[1], CREATE_TABLE_ALLOWLIST)
     errors += check_banned_create_view_as(sql, path.split(ROOT_DIR)[1])
+    errors += check_banned_include_all(sql, path.split(ROOT_DIR)[1])
 
   if errors:
     sys.stderr.write("\n".join(errors))
diff --git a/tools/cpu_profile b/tools/cpu_profile
index a5e852e..2f405da 100755
--- a/tools/cpu_profile
+++ b/tools/cpu_profile
@@ -37,18 +37,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v38.0
+# This file has been generated by: tools/roll-prebuilts v39.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        8889568,
+        9004496,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-amd64/traceconv',
     'sha256':
-        'e45d08f7050553f77c87764823c6ca26e877d0b3865f2764b33dcab6dfd0b9ee',
+        '11586dade97edf07d5b10fd5248127507fca16537f595d6530b81cbc266d1b12',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -58,11 +58,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7447928,
+        7563688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-arm64/traceconv',
     'sha256':
-        '19cabe8789566e6383632bbe79e360b8bf05465f8bb156788c8c957aba77f224',
+        '48568604b4ad119f88d847d9b306102f47a5760b923ca574dfb08b3f49bdf253',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -72,11 +72,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8633416,
+        8757640,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-amd64/traceconv',
     'sha256':
-        'a7a309c667a3e6a4c0268252540e3fc8052ebbd0abc30faf8f1783df411cf559',
+        'b263b7d4a63c923aad46940bde87063a647bb506667d857336f379d858d20646',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -86,11 +86,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6505268,
+        6605348,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm/traceconv',
     'sha256':
-        '3ff0f9d4ef43e9e76d27c69827e508f4504d2d1fcfb4dac9d739023cd72106d7',
+        'c9454182a9e53b563511e1625533644829933cec13839a6fd3f08677a0e927c0',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +100,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8108432,
+        8224984,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm64/traceconv',
     'sha256':
-        '1f4641b341f2c8bf36faf8d4befedc01bd414c8f778a5b314c99c2d42c42c068',
+        '78cd7bfa0507c43c6ab728b691c5b885e18097d761c608a31388d3f8f7d84857',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -114,55 +114,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6096120,
+        6231288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm/traceconv',
     'sha256':
-        'f15c86b1d83bd1706b81426a6d11c3274044b4838abfb5d313954c65282162a9'
+        '87f57efb365dab3b5f4230518198cf0505476a6a8376842d41403ff0e7aa13ec'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7401672,
+        7528648,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm64/traceconv',
     'sha256':
-        '8e98e30a8e137116e74390bcb9d662f0a3f22f9d38fe3fd7ef305d1051bdf19d'
+        '896d7af7632179a05ad4278482ec00ede09806d6d4c5e07d2cc7a802bc672b5b'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8238268,
+        8393916,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x86/traceconv',
     'sha256':
-        '8ce56de90b18cc01d94b26f521292d71326552f8b4fb29b919f4b2b80c3eb2ec'
+        '6ce14e83af5a4e93d8dc7d39635ccb1cd7fffe988541b94960e167d33b5c5281'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8397056,
+        8528128,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x64/traceconv',
     'sha256':
-        'e5f31569261b3f201e2cc13b8eea510627d555767272373d9196e83ddaefa3f0'
+        '11aeb6293736175c09aacff155f793eb5508ab627f612cdecf7fec7e60485d78'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        7867904,
+        8034304,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/windows-amd64/traceconv.exe',
     'sha256':
-        '74c24b2a74ae2a490ac5f918ab079a7713af07e9a36e70a2cc8536040d7df099',
+        'd53e20465cf326b5d9f5f3513cc5cd387889024ed940ec243f56dcf6fe178279',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 2a06847..eea7f09 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -895,6 +895,12 @@
   module.out.update(target.outputs)
 
   for dep in target.transitive_deps:
+    # Use globs for the chrome stdlib so the file does not need to be
+    # regenerated in autoroll CLs.
+    if dep.name.startswith(
+          '//src/trace_processor/perfetto_sql/stdlib/chrome:chrome_sql'):
+      module.srcs.add('src/trace_processor/perfetto_sql/stdlib/chrome/**/*.sql')
+      continue
     module.srcs.update(
         [gn_utils.label_to_path(src) for src in gn.get_target(dep.name).inputs])
   blueprint.add_module(module)
diff --git a/tools/heap_profile b/tools/heap_profile
index cd6770b..3979be0 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -34,18 +34,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v38.0
+# This file has been generated by: tools/roll-prebuilts v39.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        8889568,
+        9004496,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-amd64/traceconv',
     'sha256':
-        'e45d08f7050553f77c87764823c6ca26e877d0b3865f2764b33dcab6dfd0b9ee',
+        '11586dade97edf07d5b10fd5248127507fca16537f595d6530b81cbc266d1b12',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -55,11 +55,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7447928,
+        7563688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-arm64/traceconv',
     'sha256':
-        '19cabe8789566e6383632bbe79e360b8bf05465f8bb156788c8c957aba77f224',
+        '48568604b4ad119f88d847d9b306102f47a5760b923ca574dfb08b3f49bdf253',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -69,11 +69,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8633416,
+        8757640,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-amd64/traceconv',
     'sha256':
-        'a7a309c667a3e6a4c0268252540e3fc8052ebbd0abc30faf8f1783df411cf559',
+        'b263b7d4a63c923aad46940bde87063a647bb506667d857336f379d858d20646',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -83,11 +83,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6505268,
+        6605348,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm/traceconv',
     'sha256':
-        '3ff0f9d4ef43e9e76d27c69827e508f4504d2d1fcfb4dac9d739023cd72106d7',
+        'c9454182a9e53b563511e1625533644829933cec13839a6fd3f08677a0e927c0',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -97,11 +97,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8108432,
+        8224984,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm64/traceconv',
     'sha256':
-        '1f4641b341f2c8bf36faf8d4befedc01bd414c8f778a5b314c99c2d42c42c068',
+        '78cd7bfa0507c43c6ab728b691c5b885e18097d761c608a31388d3f8f7d84857',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -111,55 +111,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6096120,
+        6231288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm/traceconv',
     'sha256':
-        'f15c86b1d83bd1706b81426a6d11c3274044b4838abfb5d313954c65282162a9'
+        '87f57efb365dab3b5f4230518198cf0505476a6a8376842d41403ff0e7aa13ec'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7401672,
+        7528648,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm64/traceconv',
     'sha256':
-        '8e98e30a8e137116e74390bcb9d662f0a3f22f9d38fe3fd7ef305d1051bdf19d'
+        '896d7af7632179a05ad4278482ec00ede09806d6d4c5e07d2cc7a802bc672b5b'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8238268,
+        8393916,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x86/traceconv',
     'sha256':
-        '8ce56de90b18cc01d94b26f521292d71326552f8b4fb29b919f4b2b80c3eb2ec'
+        '6ce14e83af5a4e93d8dc7d39635ccb1cd7fffe988541b94960e167d33b5c5281'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8397056,
+        8528128,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x64/traceconv',
     'sha256':
-        'e5f31569261b3f201e2cc13b8eea510627d555767272373d9196e83ddaefa3f0'
+        '11aeb6293736175c09aacff155f793eb5508ab627f612cdecf7fec7e60485d78'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        7867904,
+        8034304,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/windows-amd64/traceconv.exe',
     'sha256':
-        '74c24b2a74ae2a490ac5f918ab079a7713af07e9a36e70a2cc8536040d7df099',
+        'd53e20465cf326b5d9f5f3513cc5cd387889024ed940ec243f56dcf6fe178279',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/record_android_trace b/tools/record_android_trace
index f91496c..6401ed8 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -33,18 +33,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v38.0
+# This file has been generated by: tools/roll-prebuilts v39.0
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1498560,
+        1498680,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-amd64/tracebox',
     'sha256':
-        'b760c7ed682d23f8d268174a939f1b8cb130ffb0d52f42b4cc4499a25423e782',
+        '07285ce963cb77212e580fe7034f6c380933c982a31272de47c7e0311d9144a1',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -54,11 +54,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1376008,
+        1376136,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-arm64/tracebox',
     'sha256':
-        '2835127a5fc42e501e29a685a1cbcd98c26f977ee845bb1bd60a43008fd48161',
+        'f56e47303fde2de737d73bc0aebddb81a624a1130ab79ca22fb9aa4f41a4e662',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -68,11 +68,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2212088,
+        2218840,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-amd64/tracebox',
     'sha256':
-        '0b4a61a3e45f4e1b6111ca0b440f9e4a0b0726df912542373f69b6821a3113bc',
+        '6a23cdbae7ecdf77b2c1f72b33350c6e4a2098627454cff4af42c027808e2be8',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -82,11 +82,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1327204,
+        1332292,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm/tracebox',
     'sha256':
-        '43c20d80e5b40cebe5b26579319be6d57796b04155ffc90f133b6a13c2de25ab',
+        '4732def5d23a3c66c3c286387488c9043d4f68d535d22af8374c3e1b9a9a61f5',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -96,11 +96,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2139928,
+        2147464,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm64/tracebox',
     'sha256':
-        '261cd7912dd69e8d82ce61b66a78f098eba46f6f492f6ec6bf73124d40a9a202',
+        'c9e83bc7b3d9059d3444392e509f96cbb242d45b0a0a27594a6b371c1a85e67f',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -110,44 +110,44 @@
     'file_name':
         'tracebox',
     'file_size':
-        1202132,
+        1230804,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm/tracebox',
     'sha256':
-        '43874c4d187e3da5820ae3988d16608a8bf89d04282b8ab3e1486c6e57cd891f'
+        '2def9ce29483c8e368cc7aa3e49ff2da60555444e6cb25c5acaec7c570b06f57'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'tracebox',
     'file_size':
-        1825448,
+        1854120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm64/tracebox',
     'sha256':
-        '9264d8f23ea3988f2952696668d3d572669134e4ca5b3e25a3209d3bfdb7d015'
+        '2484ee8210620a1dd9044610b75874302245d12aca7b59f6b7c3d55fe7ebcdde'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'tracebox',
     'file_size':
-        1820588,
+        1853356,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x86/tracebox',
     'sha256':
-        'd591e903b1e2b0e4b270f0510c24992aac68167c80e61450ebf6c36cc780cd21'
+        '7b9f8f8eb98343bf645582439c4deb9785e6b63724ff9470a703e3bf7e2522c2'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'tracebox',
     'file_size':
-        2108072,
+        2149032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x64/tracebox',
     'sha256':
-        'd3e4278e17764b605236c568202eb8d94f3f7e7593b0be9eba09f670a987acba'
+        'f81afa4516b96c3ff66cfe65b9c64567771efeef3bcb2b518a6846d358c0b93c'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/trace_processor b/tools/trace_processor
index f88419c..90eae44 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/trace_processor_shell.py
-# This file has been generated by: tools/roll-prebuilts v38.0
+# This file has been generated by: tools/roll-prebuilts v39.0
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9682976,
+        9814280,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-amd64/trace_processor_shell',
     'sha256':
-        '74b097836f16d788edce11bfda46f52e6499d8ec546d10f8dbab182612407b3b',
+        'd3b61b97f2e18aa8e6ece06b3167a012d23f895a97ed12e1f400e3f0480b72af',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8180008,
+        8295768,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-arm64/trace_processor_shell',
     'sha256':
-        '9a96a2f9ef81f210fcba4a08b21db6f2f57fb1d325e91924043ae066327c29a8',
+        '32560ee9eb8d86397fd8daec24e5e1e091e760c8cec708a1340cc7259cfd8f07',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9533320,
+        9660152,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-amd64/trace_processor_shell',
     'sha256':
-        'ee0ccae766aad09f0135efa83cc3a1cd78bd0e86824a7b1245e013d32e61d820',
+        'efbbb42291eecd0bd658694c6e60f3c7df5eee8b6e23ca6a1438059ddbd65667',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6963584,
+        7066080,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm/trace_processor_shell',
     'sha256':
-        '2c69d2016dd18b6d42bf6c2a5b4398e29e4fd294950882387942272dd25a045a',
+        'c62db9a9be13fefc114301e5a42714e400201bbf3a90d4f35c46494fc91ac230',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8950112,
+        9069000,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm64/trace_processor_shell',
     'sha256':
-        'a10a7a9a6614461beb1f32ee16da3290e2770bb6f3a506269defbdbf7e8803af',
+        'f2f5c4de02d9bd8505bcea931de8605d93485c5479130835e49b3d6ecf23fc4a',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,55 +107,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6580152,
+        6707128,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm/trace_processor_shell',
     'sha256':
-        '994a038b932accf5796550207a4e7d80c7612fd25e6eee414324e7e4f3ae14f2'
+        '5a9a5b8965f7b923444cb3ec9dea10df322a22d82765335be3191314aea48bf5'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8115560,
+        8234344,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm64/trace_processor_shell',
     'sha256':
-        '42426b12ad60894aee37e502e4d023cfded53e706d990abf5c70c4556c2b73ff'
+        'e6033a8928bc7b3ef6a5b4e56040219824dbba858b101241eb03513b39868829'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9009020,
+        9148284,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x86/trace_processor_shell',
     'sha256':
-        'af982ad7897d8cafb29a9f1303e32df20486a92eb07930db8b1898a75e84a667'
+        '38348a3295540e3c10d6622910d42844f37d7e849d3fbd8910b734df5c09f817'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        9270696,
+        9397672,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x64/trace_processor_shell',
     'sha256':
-        '081be392ddccdf37da80dd888277ea9215cfa8d05ddf6f90d9bff13a0a72f672'
+        '2f91b3c32dd48891c0979c546074b208ea7c0aa46b8ef543835d8361cb962070'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        8898560,
+        9078272,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        '2e66e5b6ab9c0f7ecad98c3b5b822133c9aa34f137b4007c228c78672fdea5a7',
+        '65e2e29e2b6c76388af17f268cf5e597896a6e135a0bbbd224174f25715f0a32',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/tracebox b/tools/tracebox
index 4425edd..6fc1a86 100755
--- a/tools/tracebox
+++ b/tools/tracebox
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py
-# This file has been generated by: tools/roll-prebuilts v38.0
+# This file has been generated by: tools/roll-prebuilts v39.0
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1498560,
+        1498680,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-amd64/tracebox',
     'sha256':
-        'b760c7ed682d23f8d268174a939f1b8cb130ffb0d52f42b4cc4499a25423e782',
+        '07285ce963cb77212e580fe7034f6c380933c982a31272de47c7e0311d9144a1',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1376008,
+        1376136,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-arm64/tracebox',
     'sha256':
-        '2835127a5fc42e501e29a685a1cbcd98c26f977ee845bb1bd60a43008fd48161',
+        'f56e47303fde2de737d73bc0aebddb81a624a1130ab79ca22fb9aa4f41a4e662',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2212088,
+        2218840,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-amd64/tracebox',
     'sha256':
-        '0b4a61a3e45f4e1b6111ca0b440f9e4a0b0726df912542373f69b6821a3113bc',
+        '6a23cdbae7ecdf77b2c1f72b33350c6e4a2098627454cff4af42c027808e2be8',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1327204,
+        1332292,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm/tracebox',
     'sha256':
-        '43c20d80e5b40cebe5b26579319be6d57796b04155ffc90f133b6a13c2de25ab',
+        '4732def5d23a3c66c3c286387488c9043d4f68d535d22af8374c3e1b9a9a61f5',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2139928,
+        2147464,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm64/tracebox',
     'sha256':
-        '261cd7912dd69e8d82ce61b66a78f098eba46f6f492f6ec6bf73124d40a9a202',
+        'c9e83bc7b3d9059d3444392e509f96cbb242d45b0a0a27594a6b371c1a85e67f',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,44 +107,44 @@
     'file_name':
         'tracebox',
     'file_size':
-        1202132,
+        1230804,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm/tracebox',
     'sha256':
-        '43874c4d187e3da5820ae3988d16608a8bf89d04282b8ab3e1486c6e57cd891f'
+        '2def9ce29483c8e368cc7aa3e49ff2da60555444e6cb25c5acaec7c570b06f57'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'tracebox',
     'file_size':
-        1825448,
+        1854120,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm64/tracebox',
     'sha256':
-        '9264d8f23ea3988f2952696668d3d572669134e4ca5b3e25a3209d3bfdb7d015'
+        '2484ee8210620a1dd9044610b75874302245d12aca7b59f6b7c3d55fe7ebcdde'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'tracebox',
     'file_size':
-        1820588,
+        1853356,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x86/tracebox',
     'sha256':
-        'd591e903b1e2b0e4b270f0510c24992aac68167c80e61450ebf6c36cc780cd21'
+        '7b9f8f8eb98343bf645582439c4deb9785e6b63724ff9470a703e3bf7e2522c2'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'tracebox',
     'file_size':
-        2108072,
+        2149032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x64/tracebox',
     'sha256':
-        'd3e4278e17764b605236c568202eb8d94f3f7e7593b0be9eba09f670a987acba'
+        'f81afa4516b96c3ff66cfe65b9c64567771efeef3bcb2b518a6846d358c0b93c'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/traceconv b/tools/traceconv
index 7ba7d3e..624e8d7 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -30,18 +30,18 @@
 
 
 # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/traceconv.py
-# This file has been generated by: tools/roll-prebuilts v38.0
+# This file has been generated by: tools/roll-prebuilts v39.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        8889568,
+        9004496,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-amd64/traceconv',
     'sha256':
-        'e45d08f7050553f77c87764823c6ca26e877d0b3865f2764b33dcab6dfd0b9ee',
+        '11586dade97edf07d5b10fd5248127507fca16537f595d6530b81cbc266d1b12',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7447928,
+        7563688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/mac-arm64/traceconv',
     'sha256':
-        '19cabe8789566e6383632bbe79e360b8bf05465f8bb156788c8c957aba77f224',
+        '48568604b4ad119f88d847d9b306102f47a5760b923ca574dfb08b3f49bdf253',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8633416,
+        8757640,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-amd64/traceconv',
     'sha256':
-        'a7a309c667a3e6a4c0268252540e3fc8052ebbd0abc30faf8f1783df411cf559',
+        'b263b7d4a63c923aad46940bde87063a647bb506667d857336f379d858d20646',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6505268,
+        6605348,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm/traceconv',
     'sha256':
-        '3ff0f9d4ef43e9e76d27c69827e508f4504d2d1fcfb4dac9d739023cd72106d7',
+        'c9454182a9e53b563511e1625533644829933cec13839a6fd3f08677a0e927c0',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8108432,
+        8224984,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/linux-arm64/traceconv',
     'sha256':
-        '1f4641b341f2c8bf36faf8d4befedc01bd414c8f778a5b314c99c2d42c42c068',
+        '78cd7bfa0507c43c6ab728b691c5b885e18097d761c608a31388d3f8f7d84857',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,55 +107,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        6096120,
+        6231288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm/traceconv',
     'sha256':
-        'f15c86b1d83bd1706b81426a6d11c3274044b4838abfb5d313954c65282162a9'
+        '87f57efb365dab3b5f4230518198cf0505476a6a8376842d41403ff0e7aa13ec'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        7401672,
+        7528648,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-arm64/traceconv',
     'sha256':
-        '8e98e30a8e137116e74390bcb9d662f0a3f22f9d38fe3fd7ef305d1051bdf19d'
+        '896d7af7632179a05ad4278482ec00ede09806d6d4c5e07d2cc7a802bc672b5b'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        8238268,
+        8393916,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x86/traceconv',
     'sha256':
-        '8ce56de90b18cc01d94b26f521292d71326552f8b4fb29b919f4b2b80c3eb2ec'
+        '6ce14e83af5a4e93d8dc7d39635ccb1cd7fffe988541b94960e167d33b5c5281'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        8397056,
+        8528128,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/android-x64/traceconv',
     'sha256':
-        'e5f31569261b3f201e2cc13b8eea510627d555767272373d9196e83ddaefa3f0'
+        '11aeb6293736175c09aacff155f793eb5508ab627f612cdecf7fec7e60485d78'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        7867904,
+        8034304,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v38.0/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v39.0/windows-amd64/traceconv.exe',
     'sha256':
-        '74c24b2a74ae2a490ac5f918ab079a7713af07e9a36e70a2cc8536040d7df099',
+        'd53e20465cf326b5d9f5f3513cc5cd387889024ed940ec243f56dcf6fe178279',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/ui/package.json b/ui/package.json
index 22ea23c..5d28841 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,7 +1,7 @@
 {
-  "name": "perfetto-ui",
+  "name": "perfetto-webui",
   "version": "1.0.0",
-  "description": "Perfetto UI",
+  "description": "Perfetto Web UI",
   "repository": "https://android.googlesource.com/platform/external/perfetto",
   "main": "main.js",
   "author": "Perfetto Team",
diff --git a/ui/src/assets/widgets/menu.scss b/ui/src/assets/widgets/menu.scss
index 523ca9f..d61bc82 100644
--- a/ui/src/assets/widgets/menu.scss
+++ b/ui/src/assets/widgets/menu.scss
@@ -14,6 +14,13 @@
 
 @import "theme";
 
+// If we're in a popup menu, remove the padding
+.pf-popup-menu.pf-popup {
+  .pf-popup-content {
+    padding: 0;
+  }
+}
+
 .pf-menu {
   display: flex;
   flex-direction: column;
diff --git a/ui/src/assets/widgets/multiselect.scss b/ui/src/assets/widgets/multiselect.scss
index b0a19fe..85eba96 100644
--- a/ui/src/assets/widgets/multiselect.scss
+++ b/ui/src/assets/widgets/multiselect.scss
@@ -27,7 +27,6 @@
   flex-direction: column;
   align-items: stretch;
   width: 280px;
-  margin: 5px;
   & > .pf-search-bar {
     margin-bottom: 8px;
     display: flex;
diff --git a/ui/src/assets/widgets/popup.scss b/ui/src/assets/widgets/popup.scss
index 5b3c0bd..0469940 100644
--- a/ui/src/assets/widgets/popup.scss
+++ b/ui/src/assets/widgets/popup.scss
@@ -28,6 +28,7 @@
   .pf-popup-content {
     // Ensures all content is rendered above the arrow
     position: relative;
+    padding: 6px;
   }
 }
 
diff --git a/ui/src/base/object_utils.ts b/ui/src/base/object_utils.ts
index 65bd95e..b4269c8 100644
--- a/ui/src/base/object_utils.ts
+++ b/ui/src/base/object_utils.ts
@@ -65,3 +65,9 @@
 export function isString(s: unknown): s is string {
   return typeof s === 'string' || s instanceof String;
 }
+
+// Given a string enum |enum|, check that |value| is a valid member of |enum|.
+export function isEnumValue<T extends {}>(
+    enm: T, value: unknown): value is T[keyof T] {
+  return Object.values(enm).includes(value);
+}
diff --git a/ui/src/base/time.ts b/ui/src/base/time.ts
index c8e5837..47c66fc 100644
--- a/ui/src/base/time.ts
+++ b/ui/src/base/time.ts
@@ -166,6 +166,24 @@
   static MAX = BigintMath.INT64_MAX;
   static ZERO = 0n;
 
+  // Cast a bigint to a |duration|. Supports potentially |undefined| values.
+  // I.e. it performs the following conversions:
+  // - `bigint` -> `duration`
+  // - `bigint|undefined` -> `duration|undefined`
+  //
+  // Use this function with caution. The function is effectively a no-op in JS,
+  // but using it tells TypeScript that "this value is a duration value". It's
+  // up to the caller to ensure the value is in the correct units.
+  //
+  // If you're reaching for this function after doing some maths on a |duration|
+  // value and it's decayed to a |bigint| consider using the static math methods
+  // in |duration| instead, as they will do the appropriate casting for you.
+  static fromRaw(v: bigint): duration;
+  static fromRaw(v?: bigint): duration|undefined;
+  static fromRaw(v?: bigint): duration|undefined {
+    return v as (duration | undefined);
+  }
+
   static min(a: duration, b: duration): duration {
     return BigintMath.min(a, b);
   }
@@ -226,6 +244,10 @@
     });
     return result.slice(0, -1);
   }
+
+  static formatSeconds(dur: duration): string {
+    return Duration.toSeconds(dur).toString() + ' s';
+  }
 }
 
 // This class takes a time and converts it to a set of strings representing a
diff --git a/ui/src/common/cache_manager.ts b/ui/src/common/cache_manager.ts
index 5890a16..b22ee05 100644
--- a/ui/src/common/cache_manager.ts
+++ b/ui/src/common/cache_manager.ts
@@ -112,7 +112,7 @@
   }
 
   const headers = new Headers([
-    ['x-trace-title', title],
+    ['x-trace-title', encodeURI(title)],
     ['x-trace-url', url],
     ['x-trace-filename', fileName],
     ['x-trace-local-only', `${localOnly}`],
@@ -140,7 +140,7 @@
   return {
     type: 'ARRAY_BUFFER',
     buffer: await response.arrayBuffer(),
-    title: response.headers.get('x-trace-title') || '',
+    title: decodeURI(response.headers.get('x-trace-title') || ''),
     fileName: response.headers.get('x-trace-filename') || undefined,
     url: response.headers.get('x-trace-url') || undefined,
     uuid: traceUuid,
diff --git a/ui/src/common/flamegraph_util.ts b/ui/src/common/flamegraph_util.ts
index baecd18..5087d04 100644
--- a/ui/src/common/flamegraph_util.ts
+++ b/ui/src/common/flamegraph_util.ts
@@ -89,6 +89,17 @@
           name: 'Total allocation count'
         },
       ];
+    case ProfileType.MIXED_HEAP_PROFILE:
+      return [
+        {
+          option: FlamegraphStateViewingOption.ALLOC_SPACE_MEMORY_ALLOCATED_KEY,
+          name: 'Total allocation size (malloc + java)'
+        },
+        {
+          option: FlamegraphStateViewingOption.OBJECTS_ALLOCATED_KEY,
+          name: 'Total allocation count (malloc + java)'
+        },
+      ];
     default:
       const exhaustiveCheck: never = profileType;
       throw new Error(`Unhandled case: ${exhaustiveCheck}`);
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 3263787..64956a0 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -167,6 +167,7 @@
 
 export enum ProfileType {
   HEAP_PROFILE = 'heap_profile',
+  MIXED_HEAP_PROFILE = 'heap_profile:com.android.art,libc.malloc',
   NATIVE_HEAP_PROFILE = 'heap_profile:libc.malloc',
   JAVA_HEAP_SAMPLES = 'heap_profile:com.android.art',
   JAVA_HEAP_GRAPH = 'graph',
diff --git a/ui/src/common/timestamp_format.ts b/ui/src/common/timestamp_format.ts
index 08f5223..c416a63 100644
--- a/ui/src/common/timestamp_format.ts
+++ b/ui/src/common/timestamp_format.ts
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {isEnumValue} from '../base/object_utils';
+
 export enum TimestampFormat {
   Timecode = 'timecode',
   Raw = 'raw',
@@ -25,13 +27,9 @@
 const TIMESTAMP_FORMAT_KEY = 'timestampFormat';
 const DEFAULT_TIMESTAMP_FORMAT = TimestampFormat.Timecode;
 
-function isTimestampFormat(value: unknown): value is TimestampFormat {
-  return Object.values(TimestampFormat).includes(value as TimestampFormat);
-}
-
 export function timestampFormat(): TimestampFormat {
   const storedFormat = localStorage.getItem(TIMESTAMP_FORMAT_KEY);
-  if (storedFormat && isTimestampFormat(storedFormat)) {
+  if (storedFormat && isEnumValue(TimestampFormat, storedFormat)) {
     timestampFormatCached = storedFormat;
   } else {
     timestampFormatCached = DEFAULT_TIMESTAMP_FORMAT;
@@ -43,3 +41,28 @@
   timestampFormatCached = format;
   localStorage.setItem(TIMESTAMP_FORMAT_KEY, format);
 }
+
+export enum DurationPrecision {
+  Full = 'full',
+  HumanReadable = 'human_readable',
+}
+
+let durationFormatCached: DurationPrecision|undefined;
+
+const DURATION_FORMAT_KEY = 'durationFormat';
+const DEFAULT_DURATION_FORMAT = DurationPrecision.Full;
+
+export function durationPrecision(): DurationPrecision {
+  const storedFormat = localStorage.getItem(DURATION_FORMAT_KEY);
+  if (storedFormat && isEnumValue(DurationPrecision, storedFormat)) {
+    durationFormatCached = storedFormat;
+  } else {
+    durationFormatCached = DEFAULT_DURATION_FORMAT;
+  }
+  return durationFormatCached;
+}
+
+export function setDurationPrecision(format: DurationPrecision) {
+  durationFormatCached = format;
+  localStorage.setItem(DURATION_FORMAT_KEY, format);
+}
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index c5b33bc..3ff39f5 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -53,6 +53,7 @@
 function getFlamegraphType(type: ProfileType) {
   switch (type) {
     case ProfileType.HEAP_PROFILE:
+    case ProfileType.MIXED_HEAP_PROFILE:
     case ProfileType.NATIVE_HEAP_PROFILE:
     case ProfileType.JAVA_HEAP_SAMPLES:
       return 'native';
@@ -61,7 +62,8 @@
     case ProfileType.PERF_SAMPLE:
       return 'perf';
     default:
-      throw new Error(`Unexpected profile type ${profileType}`);
+      const exhaustiveCheck: never = type;
+      throw new Error(`Unhandled case: ${exhaustiveCheck}`);
   }
 }
 
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index b5be573..28175b3 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -100,6 +100,7 @@
       name: STR_NULL,
       category: STR_NULL,
       id: NUM,
+      flowToDescendant: NUM,
     });
 
     const nullToStr = (s: null|string): string => {
@@ -155,6 +156,7 @@
         dur: it.endSliceStartTs - it.beginSliceEndTs,
         category,
         name,
+        flowToDescendant: !!it.flowToDescendant,
       });
     }
 
@@ -343,7 +345,8 @@
       (process_in.name || ' ' || process_in.pid) as endProcessName,
       extract_arg(f.arg_set_id, 'cat') as category,
       extract_arg(f.arg_set_id, 'name') as name,
-      f.id as id
+      f.id as id,
+      slice_is_ancestor(t1.slice_id, t2.slice_id) as flowToDescendant
     from ${connectedFlows} f
     join slice t1 on f.slice_out = t1.slice_id
     join slice t2 on f.slice_in = t2.slice_id
@@ -417,7 +420,8 @@
       NULL as endProcessName,
       extract_arg(f.arg_set_id, 'cat') as category,
       extract_arg(f.arg_set_id, 'name') as name,
-      f.id as id
+      f.id as id,
+      slice_is_ancestor(t1.slice_id, t2.slice_id) as flowToDescendant
     from flow f
     join slice t1 on f.slice_out = t1.slice_id
     join slice t2 on f.slice_in = t2.slice_id
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index 5fb6b7b..4269ae5 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -676,7 +676,11 @@
     if (profile.numRows() !== 1) return;
     const row = profile.firstRow({ts: LONG, type: STR, upid: NUM});
     const ts = Time.fromRaw(row.ts);
-    const type = profileType(row.type);
+    let profType = row.type;
+    if (profType == 'heap_profile:libc.malloc,com.android.art') {
+      profType = 'heap_profile:com.android.art,libc.malloc';
+    }
+    const type = profileType(profType);
     const upid = row.upid;
     globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
   }
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index edeeda3..ea90a3f 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -14,7 +14,6 @@
 
 import m from 'mithril';
 
-import {Duration} from '../base/time';
 import {Actions} from '../common/actions';
 import {
   AggregateData,
@@ -25,6 +24,8 @@
 import {translateState} from '../common/thread_state';
 
 import {globals} from './globals';
+import {DurationWidget} from './widgets/duration';
+
 export interface AggregationPanelAttrs {
   data: AggregateData;
   kind: string;
@@ -112,7 +113,8 @@
     if (selection === null || selection.kind !== 'AREA') return undefined;
     const selectedArea = globals.state.areas[selection.areaId];
     const duration = selectedArea.end - selectedArea.start;
-    return m('.time-range', `Selected range: ${Duration.humanise(duration)}`);
+    return m(
+        '.time-range', 'Selected range: ', m(DurationWidget, {dur: duration}));
   }
 
   // Thread state aggregation panel only
diff --git a/ui/src/frontend/app.ts b/ui/src/frontend/app.ts
index a62cbf1..500db72 100644
--- a/ui/src/frontend/app.ts
+++ b/ui/src/frontend/app.ts
@@ -29,7 +29,12 @@
 } from '../base/time';
 import {Actions} from '../common/actions';
 import {pluginManager} from '../common/plugins';
-import {setTimestampFormat, TimestampFormat} from '../common/timestamp_format';
+import {
+  DurationPrecision,
+  setDurationPrecision,
+  setTimestampFormat,
+  TimestampFormat,
+} from '../common/timestamp_format';
 import {raf} from '../core/raf_scheduler';
 import {Command} from '../public';
 import {HotkeyConfig, HotkeyContext} from '../widgets/hotkey_context';
@@ -187,7 +192,7 @@
   private cmds: Command[] = [
     {
       id: 'perfetto.SetTimestampFormat',
-      name: 'Set timestamp format',
+      name: 'Set timestamp and duration format',
       callback:
           async () => {
             const options: PromptOption[] = [
@@ -200,7 +205,7 @@
                 displayName: 'Raw (with locale-specific formatting)',
               },
             ];
-            const promptText = 'Select timecode format...';
+            const promptText = 'Select format...';
 
             try {
               const result = await this.prompt(promptText, options);
@@ -212,6 +217,29 @@
           },
     },
     {
+      id: 'perfetto.SetDurationPrecision',
+      name: 'Set duration precision',
+      callback:
+          async () => {
+            const options: PromptOption[] = [
+              {key: DurationPrecision.Full, displayName: 'Full'},
+              {
+                key: DurationPrecision.HumanReadable,
+                displayName: 'Human readable',
+              },
+            ];
+            const promptText = 'Select duration precision mode...';
+
+            try {
+              const result = await this.prompt(promptText, options);
+              setDurationPrecision(result as DurationPrecision);
+              raf.scheduleFullRedraw();
+            } catch {
+              // Prompt was probably cancelled - do nothing.
+            }
+          },
+    },
+    {
       id: 'perfetto.ShowSliceTable',
       name: 'Show slice table',
       callback:
diff --git a/ui/src/frontend/chrome_slice_details_tab.ts b/ui/src/frontend/chrome_slice_details_tab.ts
index 91eb840..7f83b36 100644
--- a/ui/src/frontend/chrome_slice_details_tab.ts
+++ b/ui/src/frontend/chrome_slice_details_tab.ts
@@ -24,7 +24,6 @@
 import {addDebugSliceTrack} from '../tracks/debug/slice_track';
 import {Button} from '../widgets/button';
 import {DetailsShell} from '../widgets/details_shell';
-import {DurationWidget} from '../widgets/duration';
 import {GridLayout, GridLayoutColumn} from '../widgets/grid_layout';
 import {MenuItem, PopupMenu2} from '../widgets/menu';
 import {Section} from '../widgets/section';
@@ -44,6 +43,7 @@
   breakDownIntervalByThreadState,
 } from './sql/thread_state';
 import {asSliceSqlId} from './sql_types';
+import {DurationWidget} from './widgets/duration';
 
 interface ContextMenuItem {
   name: string;
diff --git a/ui/src/frontend/counter_panel.ts b/ui/src/frontend/counter_panel.ts
index 3d9e8c7..9c53288 100644
--- a/ui/src/frontend/counter_panel.ts
+++ b/ui/src/frontend/counter_panel.ts
@@ -15,12 +15,12 @@
 import m from 'mithril';
 
 import {DetailsShell} from '../widgets/details_shell';
-import {DurationWidget} from '../widgets/duration';
 import {GridLayout} from '../widgets/grid_layout';
 import {Section} from '../widgets/section';
 import {Tree, TreeNode} from '../widgets/tree';
 
 import {globals} from './globals';
+import {DurationWidget} from './widgets/duration';
 import {Timestamp} from './widgets/timestamp';
 
 export class CounterDetailsPanel implements m.ClassComponent {
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index b1b7a57..4abc362 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -27,7 +27,8 @@
 import {profileType} from '../controller/flamegraph_controller';
 import {raf} from '../core/raf_scheduler';
 import {Button} from '../widgets/button';
-import {DurationWidget} from '../widgets/duration';
+import {Icon} from '../widgets/icon';
+import {Popup} from '../widgets/popup';
 
 import {Flamegraph, NodeRendering} from './flamegraph';
 import {globals} from './globals';
@@ -36,6 +37,7 @@
 import {Router} from './router';
 import {getCurrentTrace} from './sidebar';
 import {convertTraceToPprofAndDownload} from './trace_converter';
+import {DurationWidget} from './widgets/duration';
 
 const HEADER_HEIGHT = 30;
 
@@ -91,7 +93,17 @@
             [
               m('div.options',
                 [
-                  m('div.title', this.getTitle()),
+                  m('div.title',
+                    this.getTitle(),
+                    (this.profileType === ProfileType.MIXED_HEAP_PROFILE) &&
+                        m(Popup,
+                          {
+                            trigger: m(Icon, {icon: 'warning'}),
+                          },
+                          m('',
+                            {style: {width: '300px'}},
+                            'This is a mixed java/native heap profile, free()s are not visualized. To visualize free()s, remove "all_heaps: true" from the config.')),
+                    ':'),
                   this.getViewingOptionButtons(),
                 ]),
               m('div.details',
@@ -172,17 +184,20 @@
   }
 
   private getTitle(): string {
-    switch (this.profileType!) {
+    const profileType = this.profileType!;
+    switch (profileType) {
+      case ProfileType.MIXED_HEAP_PROFILE:
+        return 'Mixed heap profile';
       case ProfileType.HEAP_PROFILE:
-        return 'Heap profile:';
+        return 'Heap profile';
       case ProfileType.NATIVE_HEAP_PROFILE:
-        return 'Native heap profile:';
+        return 'Native heap profile';
       case ProfileType.JAVA_HEAP_SAMPLES:
-        return 'Java heap samples:';
+        return 'Java heap samples';
       case ProfileType.JAVA_HEAP_GRAPH:
-        return 'Java heap graph:';
+        return 'Java heap graph';
       case ProfileType.PERF_SAMPLE:
-        return 'Profile:';
+        return 'Profile';
       default:
         throw new Error('unknown type');
     }
@@ -192,9 +207,10 @@
     if (this.profileType === undefined) {
       return {};
     }
+    const profileType = this.profileType;
     const viewingOption: FlamegraphStateViewingOption =
         globals.state.currentFlamegraphState!.viewingOption;
-    switch (this.profileType) {
+    switch (profileType) {
       case ProfileType.JAVA_HEAP_GRAPH:
         if (viewingOption ===
             FlamegraphStateViewingOption.OBJECTS_ALLOCATED_NOT_FREED_KEY) {
@@ -202,13 +218,15 @@
         } else {
           return RENDER_SELF_AND_TOTAL;
         }
+      case ProfileType.MIXED_HEAP_PROFILE:
       case ProfileType.HEAP_PROFILE:
       case ProfileType.NATIVE_HEAP_PROFILE:
       case ProfileType.JAVA_HEAP_SAMPLES:
       case ProfileType.PERF_SAMPLE:
         return RENDER_SELF_AND_TOTAL;
       default:
-        throw new Error('unknown type');
+        const exhaustiveCheck: never = profileType;
+        throw new Error(`Unhandled case: ${exhaustiveCheck}`);
     }
   }
 
@@ -323,7 +341,7 @@
 
   private static selectViewingOptions(profileType: ProfileType) {
     const ret = [];
-    for (let {option, name} of viewingOptions(profileType)) {
+    for (const {option, name} of viewingOptions(profileType)) {
       ret.push(this.buildButtonComponent(option, name));
     }
     return ret;
diff --git a/ui/src/frontend/flow_events_panel.ts b/ui/src/frontend/flow_events_panel.ts
index af17ae7..7b3a006 100644
--- a/ui/src/frontend/flow_events_panel.ts
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -17,9 +17,9 @@
 import {Icons} from '../base/semantic_icons';
 import {Actions} from '../common/actions';
 import {raf} from '../core/raf_scheduler';
-import {DurationWidget} from '../widgets/duration';
 
 import {Flow, globals} from './globals';
+import {DurationWidget} from './widgets/duration';
 
 export const ALL_CATEGORIES = '_all_';
 
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index f06704e..ed47282 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -214,8 +214,14 @@
       endDir = endYConnection.y > beginYConnection.y ? 'DOWN' : 'UP';
     }
 
+
     const begin = {
-      x: this.getXCoordinate(flow.begin.sliceEndTs),
+      // If the flow goes to a descendant, we want to draw the arrow from the
+      // beginning of the slice
+      // rather from the end to avoid the flow arrow going backwards.
+      x: this.getXCoordinate(
+          flow.flowToDescendant ? flow.begin.sliceStartTs :
+                                  flow.begin.sliceEndTs),
       y: beginYConnection.y,
       dir: beginDir,
     };
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 9a45e50..0affaed 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -124,6 +124,9 @@
   end: FlowPoint;
   dur: duration;
 
+  // Whether this flow connects a slice with its descendant.
+  flowToDescendant: boolean;
+
   category?: string;
   name?: string;
 }
diff --git a/ui/src/frontend/pivot_table.ts b/ui/src/frontend/pivot_table.ts
index 2a56f8f..5880fd9 100644
--- a/ui/src/frontend/pivot_table.ts
+++ b/ui/src/frontend/pivot_table.ts
@@ -28,7 +28,6 @@
 } from '../common/state';
 import {raf} from '../core/raf_scheduler';
 import {ColumnType} from '../trace_processor/query_result';
-import {DurationWidget} from '../widgets/duration';
 
 import {addTab} from './bottom_tab';
 import {globals} from './globals';
@@ -51,6 +50,7 @@
 import {SqlTableTab} from './sql_table/tab';
 import {SqlTables} from './sql_table/well_known_tables';
 import {AttributeModalHolder} from './tables/attribute_modal_holder';
+import {DurationWidget} from './widgets/duration';
 
 interface PathItem {
   tree: PivotTree;
diff --git a/ui/src/frontend/query_table.ts b/ui/src/frontend/query_table.ts
index a19a0a6..2e675a3 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -18,7 +18,7 @@
 import {BigintMath} from '../base/bigint_math';
 import {copyToClipboard} from '../base/clipboard';
 import {isString} from '../base/object_utils';
-import {Duration, Time} from '../base/time';
+import {Time} from '../base/time';
 import {Actions} from '../common/actions';
 import {QueryResponse} from '../common/queries';
 import {Row} from '../trace_processor/query_result';
@@ -229,8 +229,7 @@
       return 'Query - running';
     }
     const result = resp.error ? 'error' : `${resp.rows.length} rows`;
-    const dur = Duration.humanise(Duration.fromMillis(resp.durationMs));
-    return `Query result (${result}) - ${dur}`;
+    return `Query result (${result}) - ${resp.durationMs.toLocaleString()}ms`;
   }
 
   renderButtons(
diff --git a/ui/src/frontend/recording/power_settings.ts b/ui/src/frontend/recording/power_settings.ts
index d88c09c..a502bc6 100644
--- a/ui/src/frontend/recording/power_settings.ts
+++ b/ui/src/frontend/recording/power_settings.ts
@@ -36,7 +36,7 @@
             {href: 'http://go/power-rails-internal-doc', target: '_blank'},
             'this doc'),
           m('span',
-            ` for instructions on how to change the refault rail selection
+            ` for instructions on how to change the default rail selection
                   on internal devices.`),
           ));
     }
diff --git a/ui/src/frontend/slice_details.ts b/ui/src/frontend/slice_details.ts
index 6056bbc..310d55c 100644
--- a/ui/src/frontend/slice_details.ts
+++ b/ui/src/frontend/slice_details.ts
@@ -16,10 +16,9 @@
 
 import {BigintMath} from '../base/bigint_math';
 import {sqliteString} from '../base/string_utils';
-import {Duration, duration, time} from '../base/time';
+import {duration, time} from '../base/time';
 import {exists} from '../base/utils';
 import {Anchor} from '../widgets/anchor';
-import {DurationWidget} from '../widgets/duration';
 import {MenuItem, PopupMenu2} from '../widgets/menu';
 import {Section} from '../widgets/section';
 import {SqlRef} from '../widgets/sql_ref';
@@ -35,12 +34,13 @@
 import {SqlTableTab} from './sql_table/tab';
 import {SqlTables} from './sql_table/well_known_tables';
 import {getProcessName, getThreadName} from './thread_and_process_info';
+import {DurationWidget} from './widgets/duration';
 import {Timestamp} from './widgets/timestamp';
 
 function computeDuration(ts: time, dur: duration): m.Children {
   if (dur === -1n) {
     const minDuration = globals.state.traceTime.end - ts;
-    return `${Duration.format(minDuration)} (Did not end)`;
+    return [m(DurationWidget, {dur: minDuration}), ' (Did not end)'];
   } else {
     return m(DurationWidget, {dur});
   }
diff --git a/ui/src/frontend/slice_details_panel.ts b/ui/src/frontend/slice_details_panel.ts
index c212249..0590184 100644
--- a/ui/src/frontend/slice_details_panel.ts
+++ b/ui/src/frontend/slice_details_panel.ts
@@ -20,7 +20,6 @@
 import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
 import {Anchor} from '../widgets/anchor';
 import {DetailsShell} from '../widgets/details_shell';
-import {DurationWidget} from '../widgets/duration';
 import {GridLayout} from '../widgets/grid_layout';
 import {Section} from '../widgets/section';
 import {SqlRef} from '../widgets/sql_ref';
@@ -29,6 +28,7 @@
 import {globals, SliceDetails, ThreadDesc} from './globals';
 import {scrollToTrackAndTs} from './scroll_helper';
 import {SlicePanel} from './slice_panel';
+import {DurationWidget} from './widgets/duration';
 import {Timestamp} from './widgets/timestamp';
 
 export class SliceDetailsPanel extends SlicePanel {
diff --git a/ui/src/frontend/slice_panel.ts b/ui/src/frontend/slice_panel.ts
index 24f8c2b..5f80ab7 100644
--- a/ui/src/frontend/slice_panel.ts
+++ b/ui/src/frontend/slice_panel.ts
@@ -15,9 +15,9 @@
 import m from 'mithril';
 
 import {duration, time} from '../base/time';
-import {DurationWidget} from '../widgets/duration';
 
 import {globals, SliceDetails} from './globals';
+import {DurationWidget} from './widgets/duration';
 
 // To display process or thread, we want to concatenate their name with ID, but
 // either can be undefined and all the cases need to be considered carefully to
diff --git a/ui/src/frontend/sql/thread_state.ts b/ui/src/frontend/sql/thread_state.ts
index d832372..e6c28d0 100644
--- a/ui/src/frontend/sql/thread_state.ts
+++ b/ui/src/frontend/sql/thread_state.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 
-import {Duration, duration, TimeSpan} from '../../base/time';
+import {duration, TimeSpan} from '../../base/time';
 import {EngineProxy} from '../../public';
 import {
   LONG,
@@ -24,6 +24,7 @@
 } from '../../trace_processor/query_result';
 import {TreeNode} from '../../widgets/tree';
 import {Utid} from '../sql_types';
+import {DurationWidget} from '../widgets/duration';
 
 // An individual node of the thread state breakdown tree.
 class Node {
@@ -124,7 +125,10 @@
       TreeNode,
       {
         left: name,
-        right: `${Duration.humanise(node.dur)} (${durPercent.toFixed(2)}%)`,
+        right: [
+          m(DurationWidget, {dur: node.dur}),
+          ` (${durPercent.toFixed(2)}%)`,
+        ],
         startsCollapsed: node.startsCollapsed,
       },
       renderChildren(node, totalDur));
diff --git a/ui/src/frontend/sql_table/render_cell.ts b/ui/src/frontend/sql_table/render_cell.ts
index beeacea..d029544 100644
--- a/ui/src/frontend/sql_table/render_cell.ts
+++ b/ui/src/frontend/sql_table/render_cell.ts
@@ -18,7 +18,7 @@
 import {isString} from '../../base/object_utils';
 import {Icons} from '../../base/semantic_icons';
 import {sqliteString} from '../../base/string_utils';
-import {duration, Duration, Time} from '../../base/time';
+import {Duration, Time} from '../../base/time';
 import {Row, SqlValue} from '../../trace_processor/query_result';
 import {Anchor} from '../../widgets/anchor';
 import {Err} from '../../widgets/error';
@@ -26,6 +26,7 @@
 import {SliceRef} from '../sql/slice';
 import {asSliceSqlId} from '../sql_types';
 import {sqlValueToString} from '../sql_utils';
+import {DurationWidget} from '../widgets/duration';
 import {Timestamp} from '../widgets/timestamp';
 
 import {Column} from './column';
@@ -84,23 +85,8 @@
   return sqlValueToString(value);
 }
 
-function displayDuration(value: duration): string;
-function displayDuration(value: SqlValue): m.Children;
-function displayDuration(value: SqlValue): m.Children {
-  if (typeof value !== 'bigint') return displayValue(value);
-  return Duration.format(value);
-}
-
 function display(column: Column, row: Row): m.Children {
   const value = row[column.alias];
-
-  // Handle all cases when we have non-trivial formatting.
-  switch (column.display?.type) {
-    case 'duration':
-    case 'thread_duration':
-      return displayDuration(value);
-  }
-
   return displayValue(value);
 }
 
@@ -119,13 +105,6 @@
   const result: m.Child[] = [];
   const value = row[column.alias];
 
-  if ((column.display?.type === 'duration' ||
-       column.display?.type === 'thread_duration') &&
-      typeof value === 'bigint') {
-    result.push(copyMenuItem('Copy raw duration', `${value}`));
-    result.push(
-        copyMenuItem('Copy formatted duration', displayDuration(value)));
-  }
   if (isString(value)) {
     result.push(copyMenuItem('Copy', value));
   }
@@ -165,6 +144,19 @@
   });
 }
 
+function renderDurationColumn(
+    column: Column, row: Row, state: SqlTableState): m.Children {
+  const value = row[column.alias];
+  if (typeof value !== 'bigint') {
+    return renderStandardColumn(column, row, state);
+  }
+
+  return m(DurationWidget, {
+    dur: Duration.fromRaw(value),
+    extraMenuItems: getContextMenuItems(column, row, state),
+  });
+}
+
 function renderSliceIdColumn(
     column: {alias: string, display: SliceIdDisplayConfig},
     row: Row): m.Children {
@@ -205,11 +197,17 @@
 
 export function renderCell(
     column: Column, row: Row, state: SqlTableState): m.Children {
-  if (column.display && column.display.type === 'slice_id') {
-    return renderSliceIdColumn(
-        {alias: column.alias, display: column.display}, row);
-  } else if (column.display && column.display.type === 'timestamp') {
-    return renderTimestampColumn(column, row, state);
+  if (column.display) {
+    switch (column.display?.type) {
+      case 'slice_id':
+        return renderSliceIdColumn(
+            {alias: column.alias, display: column.display}, row);
+      case 'timestamp':
+        return renderTimestampColumn(column, row, state);
+      case 'duration':
+      case 'thread_duration':
+        return renderDurationColumn(column, row, state);
+    }
   }
   return renderStandardColumn(column, row, state);
 }
diff --git a/ui/src/frontend/thread_state_tab.ts b/ui/src/frontend/thread_state_tab.ts
index 35d3b9f..8f43379 100644
--- a/ui/src/frontend/thread_state_tab.ts
+++ b/ui/src/frontend/thread_state_tab.ts
@@ -14,14 +14,13 @@
 
 import m from 'mithril';
 
-import {Duration, time} from '../base/time';
+import {Time, time} from '../base/time';
 import {runQuery} from '../common/queries';
 import {raf} from '../core/raf_scheduler';
 import {addDebugSliceTrack} from '../tracks/debug/slice_track';
 import {Anchor} from '../widgets/anchor';
 import {Button} from '../widgets/button';
 import {DetailsShell} from '../widgets/details_shell';
-import {DurationWidget} from '../widgets/duration';
 import {GridLayout} from '../widgets/grid_layout';
 import {Section} from '../widgets/section';
 import {SqlRef} from '../widgets/sql_ref';
@@ -42,6 +41,7 @@
   ThreadState,
   ThreadStateRef,
 } from './thread_state';
+import {DurationWidget, renderDuration} from './widgets/duration';
 import {Timestamp} from './widgets/timestamp';
 
 interface ThreadStateTabConfig {
@@ -252,7 +252,7 @@
     ];
 
     const nameForNextOrPrev = (state: ThreadState) =>
-        `${state.state} for ${Duration.humanise(state.dur)}`;
+        `${state.state} for ${renderDuration(state.dur)}`;
     return [m(
         Tree,
         this.relatedStates.waker && m(TreeNode, {
@@ -278,15 +278,15 @@
               {
                 left: 'Woken threads',
               },
-              this.relatedStates.wakee.map(
-                  (state) => m(TreeNode, ({
-                                 left: m(Timestamp, {
-                                   ts: state.ts,
-                                   display: `Start+${
-                                       Duration.humanise(state.ts - startTs)}`,
-                                 }),
-                                 right: renderRef(
-                                     state, getFullThreadName(state.thread)),
+              this.relatedStates.wakee.map((state) => m(TreeNode, ({
+                  left: m(Timestamp, {
+                    ts: state.ts,
+                    display: [
+                      'Start+',
+                      m(DurationWidget, {dur: Time.sub(state.ts, startTs)}),
+                    ],
+                  }),
+                  right: renderRef(state, getFullThreadName(state.thread)),
                   })))),
       ), m(Button,
            {
@@ -309,7 +309,7 @@
                       experimental_thread_executing_span_critical_path(
                         ${this.state?.thread?.utid},
                         trace_bounds.start_ts,
-                        trace_bounds.end_ts) cr,
+                        trace_bounds.end_ts - trace_bounds.start_ts) cr,
                       trace_bounds
                     JOIN thread USING(utid)
                     JOIN process USING(upid)
@@ -334,7 +334,7 @@
                         experimental_thread_executing_span_critical_path_stack(
                           ${this.state?.thread?.utid},
                           trace_bounds.start_ts,
-                          trace_bounds.end_ts) cr,
+                          trace_bounds.end_ts - trace_bounds.start_ts) cr,
                         trace_bounds WHERE name IS NOT NULL
                   `,
                   columns: sliceColumnNames,
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index fcbe7c0..8137c6f 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -15,7 +15,6 @@
 import m from 'mithril';
 
 import {
-  Duration,
   duration,
   Span,
   time,
@@ -37,6 +36,7 @@
   timeScaleForVisibleWindow,
 } from './gridline_helper';
 import {Panel, PanelSize} from './panel';
+import {renderDuration} from './widgets/duration';
 
 export interface BBox {
   x: number;
@@ -203,7 +203,7 @@
     const {visibleTimeScale} = globals.frontendLocalState;
     const xLeft = visibleTimeScale.timeToPx(span.start);
     const xRight = visibleTimeScale.timeToPx(span.end);
-    const label = Duration.humanise(span.duration);
+    const label = renderDuration(span.duration);
     drawHBar(
         ctx,
         {
diff --git a/ui/src/frontend/widgets/duration.ts b/ui/src/frontend/widgets/duration.ts
new file mode 100644
index 0000000..6102707
--- /dev/null
+++ b/ui/src/frontend/widgets/duration.ts
@@ -0,0 +1,134 @@
+// 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 m from 'mithril';
+
+import {copyToClipboard} from '../../base/clipboard';
+import {Icons} from '../../base/semantic_icons';
+import {Duration, duration} from '../../base/time';
+import {
+  DurationPrecision,
+  durationPrecision,
+  setDurationPrecision,
+  TimestampFormat,
+  timestampFormat,
+} from '../../common/timestamp_format';
+import {raf} from '../../core/raf_scheduler';
+import {Anchor} from '../../widgets/anchor';
+import {MenuDivider, MenuItem, PopupMenu2} from '../../widgets/menu';
+
+import {menuItemForFormat} from './timestamp';
+
+interface DurationWidgetAttrs {
+  dur: duration;
+  extraMenuItems?: m.Child[];
+}
+
+export class DurationWidget implements m.ClassComponent<DurationWidgetAttrs> {
+  view({attrs}: m.Vnode<DurationWidgetAttrs>) {
+    const {dur} = attrs;
+    return m(
+        PopupMenu2,
+        {
+          trigger: m(Anchor, renderDuration(dur)),
+        },
+        m(MenuItem, {
+          icon: Icons.Copy,
+          label: `Copy raw value`,
+          onclick: () => {
+            copyToClipboard(dur.toString());
+          },
+        }),
+        m(
+            MenuItem,
+            {
+              label: 'Set time format',
+            },
+            menuItemForFormat(TimestampFormat.Timecode, 'Timecode'),
+            menuItemForFormat(TimestampFormat.UTC, 'Realtime (UTC)'),
+            menuItemForFormat(TimestampFormat.Seconds, 'Seconds'),
+            menuItemForFormat(TimestampFormat.Raw, 'Raw'),
+            menuItemForFormat(
+                TimestampFormat.RawLocale,
+                'Raw (with locale-specific formatting)'),
+            ),
+        m(
+            MenuItem,
+            {
+              label: 'Duration precision',
+              disabled: !durationPrecisionHasEffect(),
+              title: 'Not configurable with current time format',
+            },
+            menuItemForPrecision(DurationPrecision.Full, 'Full'),
+            menuItemForPrecision(
+                DurationPrecision.HumanReadable, 'Human readable'),
+            ),
+        attrs.extraMenuItems ? [m(MenuDivider), attrs.extraMenuItems] : null,
+    );
+  }
+}
+
+function menuItemForPrecision(
+    value: DurationPrecision, label: string): m.Children {
+  return m(MenuItem, {
+    label,
+    active: value === durationPrecision(),
+    onclick: () => {
+      setDurationPrecision(value);
+      raf.scheduleFullRedraw();
+    },
+  });
+}
+
+function durationPrecisionHasEffect(): boolean {
+  switch (timestampFormat()) {
+    case TimestampFormat.Timecode:
+    case TimestampFormat.UTC:
+      return true;
+    default:
+      return false;
+  }
+}
+
+
+export function renderDuration(dur: duration): string {
+  const fmt = timestampFormat();
+  switch (fmt) {
+    case TimestampFormat.UTC:
+    case TimestampFormat.Timecode:
+      return renderFormattedDuration(dur);
+    case TimestampFormat.Raw:
+      return dur.toString();
+    case TimestampFormat.RawLocale:
+      return dur.toLocaleString();
+    case TimestampFormat.Seconds:
+      return Duration.formatSeconds(dur);
+    default:
+      const x: never = fmt;
+      throw new Error(`Invalid format ${x}`);
+  }
+}
+
+function renderFormattedDuration(dur: duration): string {
+  const fmt = durationPrecision();
+  switch (fmt) {
+    case DurationPrecision.HumanReadable:
+      return Duration.humanise(dur);
+    case DurationPrecision.Full:
+      return Duration.format(dur);
+    default:
+      const x: never = fmt;
+      throw new Error(`Invalid format ${x}`);
+  }
+}
diff --git a/ui/src/frontend/widgets/timestamp.ts b/ui/src/frontend/widgets/timestamp.ts
index a4abc38..81d2fa8 100644
--- a/ui/src/frontend/widgets/timestamp.ts
+++ b/ui/src/frontend/widgets/timestamp.ts
@@ -18,9 +18,14 @@
 import {Icons} from '../../base/semantic_icons';
 import {time, Time} from '../../base/time';
 import {Actions} from '../../common/actions';
-import {TimestampFormat, timestampFormat} from '../../common/timestamp_format';
+import {
+  setTimestampFormat,
+  TimestampFormat,
+  timestampFormat,
+} from '../../common/timestamp_format';
+import {raf} from '../../core/raf_scheduler';
 import {Anchor} from '../../widgets/anchor';
-import {MenuItem, PopupMenu2} from '../../widgets/menu';
+import {MenuDivider, MenuItem, PopupMenu2} from '../../widgets/menu';
 import {globals} from '../globals';
 
 // import {MenuItem, PopupMenu2} from './menu';
@@ -61,11 +66,36 @@
             copyToClipboard(ts.toString());
           },
         }),
-        ...(attrs.extraMenuItems ?? []),
+        m(
+            MenuItem,
+            {
+              label: 'Time format',
+            },
+            menuItemForFormat(TimestampFormat.Timecode, 'Timecode'),
+            menuItemForFormat(TimestampFormat.UTC, 'Realtime (UTC)'),
+            menuItemForFormat(TimestampFormat.Seconds, 'Seconds'),
+            menuItemForFormat(TimestampFormat.Raw, 'Raw'),
+            menuItemForFormat(
+                TimestampFormat.RawLocale,
+                'Raw (with locale-specific formatting)'),
+            ),
+        attrs.extraMenuItems ? [m(MenuDivider), attrs.extraMenuItems] : null,
     );
   }
 }
 
+export function menuItemForFormat(
+    value: TimestampFormat, label: string): m.Children {
+  return m(MenuItem, {
+    label,
+    active: value === timestampFormat(),
+    onclick: () => {
+      setTimestampFormat(value);
+      raf.scheduleFullRedraw();
+    },
+  });
+}
+
 function renderTimestamp(time: time): m.Children {
   const fmt = timestampFormat();
   const domainTime = globals.toDomainTime(time);
diff --git a/ui/src/tracks/chrome_critical_user_interactions/details_panel.ts b/ui/src/tracks/chrome_critical_user_interactions/details_panel.ts
deleted file mode 100644
index f58e2e9..0000000
--- a/ui/src/tracks/chrome_critical_user_interactions/details_panel.ts
+++ /dev/null
@@ -1,236 +0,0 @@
-// 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 m from 'mithril';
-
-import {duration, Time, time} from '../../base/time';
-import {exists} from '../../base/utils';
-import {raf} from '../../core/raf_scheduler';
-import {
-  BottomTab,
-  bottomTabRegistry,
-  NewBottomTabArgs,
-} from '../../frontend/bottom_tab';
-import {
-  GenericSliceDetailsTabConfig,
-} from '../../frontend/generic_slice_details_tab';
-import {sqlValueToString} from '../../frontend/sql_utils';
-import {Timestamp} from '../../frontend/widgets/timestamp';
-import {LONG, LONG_NULL, NUM, STR} from '../../trace_processor/query_result';
-import {Anchor} from '../../widgets/anchor';
-import {DetailsShell} from '../../widgets/details_shell';
-import {DurationWidget} from '../../widgets/duration';
-import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
-import {Section} from '../../widgets/section';
-import {SqlRef} from '../../widgets/sql_ref';
-import {dictToTreeNodes, Tree} from '../../widgets/tree';
-
-interface PageLoadMetrics {
-  url: string;
-  navigationId: number;
-  fcpDuration?: duration;
-  lcpDuration?: duration;
-  fcpTs: time, lcpTs?: time,
-}
-
-enum CriticalUserJourneyType {
-  UNKNOWN = 'Unknown',
-  PAGE_LOAD = 'PageLoad',
-}
-
-function convertToCriticalUserJourneyType(cujType: string):
-    CriticalUserJourneyType {
-  switch (cujType) {
-    case CriticalUserJourneyType.PAGE_LOAD:
-      return CriticalUserJourneyType.PAGE_LOAD;
-    default:
-      return CriticalUserJourneyType.UNKNOWN;
-  }
-}
-
-interface Data {
-  name: string;
-  // Timestamp of the beginning of this slice in nanoseconds.
-  ts: time;
-  // Duration of this slice in nanoseconds.
-  dur: duration;
-  type: CriticalUserJourneyType;
-  tableName: string;
-  // Metrics for |type| = CriticalUserJourney.PAGE_LOAD
-  pageLoadMetrics?: PageLoadMetrics;
-}
-
-export class CriticalUserInteractionDetailsPanel extends
-    BottomTab<GenericSliceDetailsTabConfig> {
-  static readonly kind = 'org.perfetto.CriticalUserInteractionDetailsPanel';
-  data: Data|undefined;
-  loaded = false;
-
-  static create(args: NewBottomTabArgs): CriticalUserInteractionDetailsPanel {
-    return new CriticalUserInteractionDetailsPanel(args);
-  }
-
-  constructor(args: NewBottomTabArgs) {
-    super(args);
-    this.loadData();
-  }
-
-  private async loadData() {
-    const queryResult = await this.engine.query(`
-      SELECT
-        name,
-        ts,
-        dur,
-        type AS tableName
-      FROM chrome_interactions
-      WHERE scoped_id = ${this.config.id}`);
-
-    const iter = queryResult.firstRow({
-      name: STR,
-      ts: LONG,
-      dur: LONG,
-      tableName: STR,
-    });
-
-    this.data = {
-      name: iter.name,
-      ts: Time.fromRaw(iter.ts),
-      dur: iter.dur,
-      type: convertToCriticalUserJourneyType(iter.name),
-      tableName: iter.tableName,
-    };
-
-    await this.loadMetrics();
-
-    this.loaded = true;
-    raf.scheduleFullRedraw();
-  }
-
-  private async loadMetrics() {
-    if (exists(this.data)) {
-      switch (this.data.type) {
-        case CriticalUserJourneyType.PAGE_LOAD:
-          await this.loadPageLoadMetrics();
-          break;
-        default:
-          break;
-      }
-    }
-  }
-
-  private async loadPageLoadMetrics() {
-    if (exists(this.data)) {
-      const queryResult = await this.engine.query(`
-      SELECT
-        navigation_id AS navigationId,
-        url,
-        fcp AS fcpDuration,
-        lcp AS lcpDuration,
-        fcp_ts AS fcpTs,
-        lcp_ts AS lcpTs
-      FROM chrome_page_loads
-      WHERE navigation_id = ${this.config.id}`);
-
-      const iter = queryResult.firstRow({
-        navigationId: NUM,
-        url: STR,
-        fcpDuration: LONG_NULL,
-        lcpDuration: LONG_NULL,
-        fcpTs: LONG,
-        lcpTs: LONG,
-      });
-
-      this.data.pageLoadMetrics = {
-        navigationId: iter.navigationId,
-        url: iter.url,
-        fcpTs: Time.fromRaw(iter.fcpTs),
-      };
-
-      if (exists(iter.fcpDuration)) {
-        this.data.pageLoadMetrics.fcpDuration = iter.fcpDuration;
-      }
-
-      if (exists(iter.lcpDuration)) {
-        this.data.pageLoadMetrics.lcpDuration = iter.lcpDuration;
-      }
-
-      if (Number(iter.lcpTs) != 0) {
-        this.data.pageLoadMetrics.lcpTs = Time.fromRaw(iter.lcpTs);
-      }
-    }
-  }
-
-  private renderDetailsDictionary(): m.Child[] {
-    const details: {[key: string]: m.Child} = {};
-    if (exists(this.data)) {
-      details['Name'] = sqlValueToString(this.data.name);
-      details['Timestamp'] = m(Timestamp, {ts: this.data.ts});
-      if (exists(this.data.pageLoadMetrics)) {
-        details['FCP Timestamp'] =
-            m(Timestamp, {ts: this.data.pageLoadMetrics.fcpTs});
-        if (exists(this.data.pageLoadMetrics.fcpDuration)) {
-          details['FCP Duration'] =
-              m(DurationWidget, {dur: this.data.pageLoadMetrics.fcpDuration});
-        }
-        if (exists(this.data.pageLoadMetrics.lcpTs)) {
-          details['LCP Timestamp'] =
-              m(Timestamp, {ts: this.data.pageLoadMetrics.lcpTs});
-        }
-        if (exists(this.data.pageLoadMetrics.lcpDuration)) {
-          details['LCP Duration'] =
-              m(DurationWidget, {dur: this.data.pageLoadMetrics.lcpDuration});
-        }
-        details['Navigation ID'] = this.data.pageLoadMetrics.navigationId;
-        const url = this.data.pageLoadMetrics.url;
-        details['URL'] =
-            m(Anchor, {href: url, target: '_blank', icon: 'open_in_new'}, url);
-      }
-      details['SQL ID'] =
-          m(SqlRef, {table: 'chrome_interactions', id: this.config.id});
-    }
-
-    return dictToTreeNodes(details);
-  }
-
-  viewTab() {
-    if (this.data === undefined) {
-      return m('h2', 'Loading');
-    }
-
-    return m(
-        DetailsShell,
-        {
-          title: this.getTitle(),
-        },
-        m(GridLayout,
-          m(
-              GridLayoutColumn,
-              m(
-                  Section,
-                  {title: 'Details'},
-                  m(Tree, this.renderDetailsDictionary()),
-                  ),
-              )));
-  }
-
-  getTitle(): string {
-    return this.config.title;
-  }
-
-  isLoading() {
-    return !this.loaded;
-  }
-}
-
-bottomTabRegistry.register(CriticalUserInteractionDetailsPanel);
diff --git a/ui/src/tracks/chrome_critical_user_interactions/index.ts b/ui/src/tracks/chrome_critical_user_interactions/index.ts
index b77fc31..f77b967 100644
--- a/ui/src/tracks/chrome_critical_user_interactions/index.ts
+++ b/ui/src/tracks/chrome_critical_user_interactions/index.ts
@@ -16,8 +16,14 @@
 
 import {Actions} from '../../common/actions';
 import {SCROLLING_TRACK_GROUP} from '../../common/state';
+import {OnSliceClickArgs} from '../../frontend/base_slice_track';
+import {GenericSliceDetailsTab} from '../../frontend/generic_slice_details_tab';
 import {globals} from '../../frontend/globals';
-import {NamedSliceTrackTypes} from '../../frontend/named_slice_track';
+import {
+  NAMED_SLICE_ROW,
+  NamedSliceTrackTypes,
+} from '../../frontend/named_slice_track';
+import {Slice} from '../../frontend/slice';
 import {NewTrackArgs, TrackBase} from '../../frontend/track';
 import {
   Plugin,
@@ -25,6 +31,7 @@
   PluginContextTrace,
   PluginDescriptor,
   PrimaryTrackSortKey,
+  STR,
 } from '../../public';
 import {
   CustomSqlDetailsPanelConfig,
@@ -33,13 +40,45 @@
   CustomSqlTableSliceTrack,
 } from '../custom_sql_table_slices';
 
-import {CriticalUserInteractionDetailsPanel} from './details_panel';
+import {PageLoadDetailsPanel} from './page_load_details_panel';
 
 export const CRITICAL_USER_INTERACTIONS_KIND =
-    'org.chromium.TopLevelScrolls.scrolls';
+    'org.chromium.CriticalUserInteraction.track';
+
+export const CRITICAL_USER_INTERACTIONS_SLICE_ROW = {
+  ...NAMED_SLICE_ROW,
+  type: STR,
+};
+export type CriticalUserInteractionSliceRow =
+    typeof CRITICAL_USER_INTERACTIONS_SLICE_ROW;
+
+export interface CriticalUserInteractionSlice extends Slice {
+  type: string;
+}
+
+export interface CriticalUserInteractionSliceTrackTypes extends
+    NamedSliceTrackTypes {
+  slice: CriticalUserInteractionSlice;
+  row: CriticalUserInteractionSliceRow;
+}
+
+enum CriticalUserInteractionType {
+  UNKNOWN = 'Unknown',
+  PAGE_LOAD = 'chrome_page_loads',
+}
+
+function convertToCriticalUserInteractionType(cujType: string):
+    CriticalUserInteractionType {
+  switch (cujType) {
+    case CriticalUserInteractionType.PAGE_LOAD:
+      return CriticalUserInteractionType.PAGE_LOAD;
+    default:
+      return CriticalUserInteractionType.UNKNOWN;
+  }
+}
 
 export class CriticalUserInteractionTrack extends
-    CustomSqlTableSliceTrack<NamedSliceTrackTypes> {
+    CustomSqlTableSliceTrack<CriticalUserInteractionSliceTrackTypes> {
   static readonly kind = CRITICAL_USER_INTERACTIONS_KIND;
 
   static create(args: NewTrackArgs): TrackBase {
@@ -53,14 +92,31 @@
     };
   }
 
-  getDetailsPanel(): CustomSqlDetailsPanelConfig {
-    return {
-      kind: CriticalUserInteractionDetailsPanel.kind,
+  getDetailsPanel(
+      args: OnSliceClickArgs<CriticalUserInteractionSliceTrackTypes['slice']>):
+      CustomSqlDetailsPanelConfig {
+    let detailsPanel = {
+      kind: GenericSliceDetailsTab.kind,
       config: {
         sqlTableName: this.tableName,
-        title: 'Chrome Critical User Interaction',
+        title: 'Chrome Interaction',
       },
     };
+
+    switch (convertToCriticalUserInteractionType(args.slice.type)) {
+      case CriticalUserInteractionType.PAGE_LOAD:
+        detailsPanel = {
+          kind: PageLoadDetailsPanel.kind,
+          config: {
+            sqlTableName: this.tableName,
+            title: 'Chrome Page Load',
+          },
+        };
+        break;
+      default:
+        break;
+    }
+    return detailsPanel;
   }
 
   getSqlImports(): CustomSqlImportConfig {
@@ -68,6 +124,17 @@
       modules: ['chrome.interactions'],
     };
   }
+
+  getRowSpec(): CriticalUserInteractionSliceTrackTypes['row'] {
+    return CRITICAL_USER_INTERACTIONS_SLICE_ROW;
+  }
+
+  rowToSlice(row: CriticalUserInteractionSliceTrackTypes['row']):
+      CriticalUserInteractionSliceTrackTypes['slice'] {
+    const baseSlice = super.rowToSlice(row);
+    const type = row.type;
+    return {...baseSlice, type};
+  }
 }
 
 export function addCriticalUserInteractionTrack() {
diff --git a/ui/src/tracks/chrome_critical_user_interactions/page_load_details_panel.ts b/ui/src/tracks/chrome_critical_user_interactions/page_load_details_panel.ts
new file mode 100644
index 0000000..bc2a8a4
--- /dev/null
+++ b/ui/src/tracks/chrome_critical_user_interactions/page_load_details_panel.ts
@@ -0,0 +1,213 @@
+// 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 m from 'mithril';
+
+import {duration, time, Time} from '../../base/time';
+import {exists} from '../../base/utils';
+import {raf} from '../../core/raf_scheduler';
+import {
+  BottomTab,
+  bottomTabRegistry,
+  NewBottomTabArgs,
+} from '../../frontend/bottom_tab';
+import {
+  GenericSliceDetailsTabConfig,
+} from '../../frontend/generic_slice_details_tab';
+import {asUpid, Upid} from '../../frontend/sql_types';
+import {DurationWidget} from '../../frontend/widgets/duration';
+import {Timestamp} from '../../frontend/widgets/timestamp';
+import {LONG, LONG_NULL, NUM, STR} from '../../trace_processor/query_result';
+import {Anchor} from '../../widgets/anchor';
+import {DetailsShell} from '../../widgets/details_shell';
+import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
+import {Section} from '../../widgets/section';
+import {SqlRef} from '../../widgets/sql_ref';
+import {dictToTreeNodes, Tree} from '../../widgets/tree';
+
+interface Data {
+  ts: time;
+  url: string;
+  // The row id in the chrome_page_loads table is the unique identifier of the
+  // combination of navigation id and browser upid; otherwise, navigation id
+  // is not guaranteed to be unique in a trace.
+  id: number;
+  navigationId: number;
+  upid: Upid;
+  fcpDuration: duration;
+  lcpDuration?: duration;
+  fcpTs: time;
+  lcpTs?: time;
+  domContentLoadedTs?: time;
+  loadTs?: time;
+  markFullyLoadedTs?: time;
+  markFullyVisibleTs?: time;
+  markInteractiveTs?: time;
+}
+
+export class PageLoadDetailsPanel extends
+    BottomTab<GenericSliceDetailsTabConfig> {
+  static readonly kind = 'org.perfetto.PageLoadDetailsPanel';
+  private loaded = false;
+  private data: Data|undefined;
+
+  static create(args: NewBottomTabArgs): PageLoadDetailsPanel {
+    return new PageLoadDetailsPanel(args);
+  }
+
+  constructor(args: NewBottomTabArgs) {
+    super(args);
+    this.loadData();
+  }
+
+  private async loadData() {
+    const queryResult = await this.engine.query(`
+      SELECT
+        id,
+        navigation_id AS navigationId,
+        browser_upid AS upid,
+        navigation_start_ts AS ts,
+        url,
+        fcp AS fcpDuration,
+        lcp AS lcpDuration,
+        fcp_ts AS fcpTs,
+        lcp_ts AS lcpTs,
+        dom_content_loaded_event_ts AS domContentLoadedTs,
+        load_event_ts AS loadTs,
+        mark_fully_loaded_ts AS markFullyLoadedTs,
+        mark_fully_visible_ts AS markFullyVisibleTs,
+        mark_interactive_ts AS markInteractiveTs
+      FROM chrome_page_loads
+      WHERE id = ${this.config.id};`);
+
+    const iter = queryResult.firstRow({
+      id: NUM,
+      navigationId: NUM,
+      upid: NUM,
+      ts: LONG,
+      url: STR,
+      fcpDuration: LONG,
+      lcpDuration: LONG_NULL,
+      fcpTs: LONG,
+      lcpTs: LONG_NULL,
+      domContentLoadedTs: LONG_NULL,
+      loadTs: LONG_NULL,
+      markFullyLoadedTs: LONG_NULL,
+      markFullyVisibleTs: LONG_NULL,
+      markInteractiveTs: LONG_NULL,
+    });
+
+    this.data = {
+      id: iter.id,
+      ts: Time.fromRaw(iter.ts),
+      fcpTs: Time.fromRaw(iter.fcpTs),
+      fcpDuration: iter.fcpDuration,
+      navigationId: iter.navigationId,
+      upid: asUpid(iter.upid),
+      url: iter.url,
+      lcpTs: Time.fromRaw(iter.lcpTs ?? undefined),
+      lcpDuration: iter.lcpDuration ?? undefined,
+      domContentLoadedTs: Time.fromRaw(iter.domContentLoadedTs ?? undefined),
+      loadTs: Time.fromRaw(iter.loadTs ?? undefined),
+      markFullyLoadedTs: Time.fromRaw(iter.markFullyLoadedTs ?? undefined),
+      markFullyVisibleTs: Time.fromRaw(iter.markFullyVisibleTs ?? undefined),
+      markInteractiveTs: Time.fromRaw(iter.markInteractiveTs ?? undefined),
+    };
+
+    this.loaded = true;
+    raf.scheduleFullRedraw();
+  }
+
+  private getDetailsDictionary() {
+    const details: {[key: string]: m.Child} = {};
+    if (exists(this.data)) {
+      details['Timestamp'] = m(Timestamp, {ts: this.data.ts});
+      details['FCP Timestamp'] = m(Timestamp, {ts: this.data.fcpTs});
+      details['FCP Duration'] = m(DurationWidget, {dur: this.data.fcpDuration});
+
+      if (exists(this.data.lcpTs)) {
+        details['LCP Timestamp'] = m(Timestamp, {ts: this.data.lcpTs});
+      }
+      if (exists(this.data.lcpDuration)) {
+        details['LCP Duration'] =
+            m(DurationWidget, {dur: this.data.lcpDuration});
+      }
+
+      if (exists(this.data.domContentLoadedTs)) {
+        details['DOM Content Loaded Event Timestamp'] =
+            m(Timestamp, {ts: this.data.domContentLoadedTs});
+      }
+
+      if (exists(this.data.loadTs)) {
+        details['Load Timestamp'] = m(Timestamp, {ts: this.data.loadTs});
+      }
+
+      if (exists(this.data.markFullyLoadedTs)) {
+        details['Page Timing Mark Fully Loaded Timestamp'] =
+            m(Timestamp, {ts: this.data.markFullyLoadedTs});
+      }
+
+      if (exists(this.data.markFullyVisibleTs)) {
+        details['Page Timing Mark Fully Visible Timestamp'] =
+            m(Timestamp, {ts: this.data.markFullyVisibleTs});
+      }
+
+      if (exists(this.data.markInteractiveTs)) {
+        details['Page Timing Mark Interactive Timestamp'] =
+            m(Timestamp, {ts: this.data.markInteractiveTs});
+      }
+
+      details['Navigation ID'] = this.data.navigationId;
+      details['Browser Upid'] = this.data.upid;
+      details['URL'] =
+          m(Anchor,
+            {href: this.data.url, target: '_blank', icon: 'open_in_new'},
+            this.data.url);
+      details['SQL ID'] =
+          m(SqlRef, {table: 'chrome_page_loads', id: this.data.id});
+    }
+    return details;
+  }
+
+  viewTab() {
+    if (this.isLoading()) {
+      return m('h2', 'Loading');
+    }
+
+    return m(
+        DetailsShell,
+        {
+          title: this.getTitle(),
+        },
+        m(GridLayout,
+          m(
+              GridLayoutColumn,
+              m(
+                  Section,
+                  {title: 'Details'},
+                  m(Tree, dictToTreeNodes(this.getDetailsDictionary())),
+                  ),
+              )));
+  }
+
+  getTitle(): string {
+    return this.config.title;
+  }
+
+  isLoading() {
+    return !this.loaded;
+  }
+}
+
+bottomTabRegistry.register(PageLoadDetailsPanel);
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
index c659f20..63e913f 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_details_panel.ts
@@ -32,10 +32,10 @@
   Table,
   TableData,
 } from '../../frontend/tables/table';
+import {DurationWidget} from '../../frontend/widgets/duration';
 import {Timestamp} from '../../frontend/widgets/timestamp';
 import {LONG, NUM, STR} from '../../trace_processor/query_result';
 import {DetailsShell} from '../../widgets/details_shell';
-import {DurationWidget} from '../../widgets/duration';
 import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
 import {Section} from '../../widgets/section';
 import {SqlRef} from '../../widgets/sql_ref';
diff --git a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
index 13c908b..d130151 100644
--- a/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
+++ b/ui/src/tracks/chrome_scroll_jank/scroll_jank_v3_details_panel.ts
@@ -28,11 +28,11 @@
 import {getSlice, SliceDetails} from '../../frontend/sql/slice';
 import {asSliceSqlId} from '../../frontend/sql_types';
 import {sqlValueToString} from '../../frontend/sql_utils';
+import {DurationWidget} from '../../frontend/widgets/duration';
 import {Timestamp} from '../../frontend/widgets/timestamp';
 import {EngineProxy} from '../../trace_processor/engine';
 import {LONG, NUM, STR} from '../../trace_processor/query_result';
 import {DetailsShell} from '../../widgets/details_shell';
-import {DurationWidget} from '../../widgets/duration';
 import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
 import {Section} from '../../widgets/section';
 import {SqlRef} from '../../widgets/sql_ref';
diff --git a/ui/src/tracks/custom_sql_table_slices/index.ts b/ui/src/tracks/custom_sql_table_slices/index.ts
index 8389a8e..40c980a 100644
--- a/ui/src/tracks/custom_sql_table_slices/index.ts
+++ b/ui/src/tracks/custom_sql_table_slices/index.ts
@@ -64,7 +64,9 @@
   abstract getSqlDataSource(): CustomSqlTableDefConfig;
 
   // Override by subclasses.
-  abstract getDetailsPanel(): CustomSqlDetailsPanelConfig;
+  abstract getDetailsPanel(args:
+                               OnSliceClickArgs<NamedSliceTrackTypes['slice']>):
+      CustomSqlDetailsPanelConfig;
 
   getSqlImports(): CustomSqlImportConfig {
     return {
@@ -106,11 +108,11 @@
   }
 
   onSliceClick(args: OnSliceClickArgs<NamedSliceTrackTypes['slice']>) {
-    if (this.getDetailsPanel() === undefined) {
+    if (this.getDetailsPanel(args) === undefined) {
       return;
     }
 
-    const detailsPanelConfig = this.getDetailsPanel();
+    const detailsPanelConfig = this.getDetailsPanel(args);
     globals.makeSelection(Actions.selectGenericSlice({
       id: args.slice.id,
       sqlTableName: this.tableName,
diff --git a/ui/src/tracks/debug/details_tab.ts b/ui/src/tracks/debug/details_tab.ts
index 8c9bc33..3523f4f 100644
--- a/ui/src/tracks/debug/details_tab.ts
+++ b/ui/src/tracks/debug/details_tab.ts
@@ -43,6 +43,7 @@
   ThreadState,
   threadStateRef,
 } from '../../frontend/thread_state';
+import {DurationWidget} from '../../frontend/widgets/duration';
 import {Timestamp} from '../../frontend/widgets/timestamp';
 import {
   ColumnType,
@@ -52,7 +53,6 @@
   timeFromSql,
 } from '../../trace_processor/query_result';
 import {DetailsShell} from '../../widgets/details_shell';
-import {DurationWidget} from '../../widgets/duration';
 import {GridLayout} from '../../widgets/grid_layout';
 import {Section} from '../../widgets/section';
 import {
diff --git a/ui/src/tracks/heap_profile/index.ts b/ui/src/tracks/heap_profile/index.ts
index 7b73dec..25617c5 100644
--- a/ui/src/tracks/heap_profile/index.ts
+++ b/ui/src/tracks/heap_profile/index.ts
@@ -85,7 +85,12 @@
     const it = queryRes.iter({ts: LONG, type: STR});
     for (let row = 0; it.valid(); it.next(), row++) {
       data.tsStarts[row] = it.ts;
-      data.types[row] = profileType(it.type);
+
+      let profType = it.type;
+      if (profType == 'heap_profile:libc.malloc,com.android.art') {
+        profType = 'heap_profile:com.android.art,libc.malloc';
+      }
+      data.types[row] = profileType(profType);
     }
     return data;
   }
diff --git a/ui/src/widgets/duration.ts b/ui/src/widgets/duration.ts
deleted file mode 100644
index 2ae19cb..0000000
--- a/ui/src/widgets/duration.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-// 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 m from 'mithril';
-
-import {Duration, duration} from '../base/time';
-
-interface DurationWidgetAttrs {
-  dur: duration;
-}
-
-export class DurationWidget implements m.ClassComponent<DurationWidgetAttrs> {
-  view(vnode: m.Vnode<DurationWidgetAttrs>) {
-    return Duration.format(vnode.attrs.dur);
-  }
-}
diff --git a/ui/src/widgets/menu.ts b/ui/src/widgets/menu.ts
index db358f8..275f07c 100644
--- a/ui/src/widgets/menu.ts
+++ b/ui/src/widgets/menu.ts
@@ -163,6 +163,7 @@
         {
           trigger,
           position: popupPosition,
+          className: 'pf-popup-menu',
           ...popupAttrs,
         },
         m(Menu, children));