Reduce diff between upstream and downstream attempt 3 am: c254cef39a am: 56ac760239

Original change: https://googleplex-android-review.googlesource.com/c/platform/external/perfetto/+/23263017

Change-Id: I841624cf04b125d5702da7ba9b0669816ac0a02b
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.clang-tidy b/.clang-tidy
index 07073df..8a822c2 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,4 +1,4 @@
-Checks: android-cloexec-*,bugprone-*,google-explicit-constructor,android-comparison-in-temp-failure-retry,modernize-use-nullptr,performance-for-range-copy,performance-noexcept-move-constructor,readability-container-size-empty,readability-else-after-return
+Checks: android-cloexec-*,bugprone-*,-bugprone-easily-swappable-parameters,google-explicit-constructor,android-comparison-in-temp-failure-retry,modernize-use-nullptr,performance-for-range-copy,performance-noexcept-move-constructor,readability-container-size-empty,readability-else-after-return
 CheckOptions:
   - key:             bugprone-assert-side-effect.AssertMacros
     value:           'PERFETTO_DCHECK'
diff --git a/Android.bp b/Android.bp
index f12c1c3..f4f9a2d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -593,6 +593,7 @@
         ":perfetto_src_tracing_common",
         ":perfetto_src_tracing_core_core",
         ":perfetto_src_tracing_core_service",
+        ":perfetto_src_tracing_core_zlib_compressor",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
@@ -660,10 +661,19 @@
     defaults: [
         "perfetto_defaults",
     ],
+    cflags: [
+        "-DZLIB_IMPLEMENTATION",
+    ],
     target: {
         android: {
             shared_libs: [
                 "liblog",
+                "libz",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libz",
             ],
         },
     },
@@ -1735,6 +1745,11 @@
     name: "perfetto_include_perfetto_ext_base_version",
 }
 
+// GN: //include/perfetto/ext/cloud_trace_processor:cloud_trace_processor
+filegroup {
+    name: "perfetto_include_perfetto_ext_cloud_trace_processor_cloud_trace_processor",
+}
+
 // GN: //include/perfetto/ext/ipc:ipc
 filegroup {
     name: "perfetto_include_perfetto_ext_ipc_ipc",
@@ -2049,11 +2064,13 @@
         ":perfetto_src_trace_processor_metatrace",
         ":perfetto_src_trace_processor_metrics_metrics",
         ":perfetto_src_trace_processor_prelude_functions_functions",
+        ":perfetto_src_trace_processor_prelude_functions_interface",
         ":perfetto_src_trace_processor_prelude_operators_operators",
+        ":perfetto_src_trace_processor_prelude_table_functions_interface",
         ":perfetto_src_trace_processor_prelude_table_functions_table_functions",
         ":perfetto_src_trace_processor_sorter_sorter",
+        ":perfetto_src_trace_processor_sqlite_query_constraints",
         ":perfetto_src_trace_processor_sqlite_sqlite",
-        ":perfetto_src_trace_processor_sqlite_sqlite_minimal",
         ":perfetto_src_trace_processor_storage_minimal",
         ":perfetto_src_trace_processor_storage_storage",
         ":perfetto_src_trace_processor_tables_tables",
@@ -2248,6 +2265,8 @@
         "perfetto_src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+        "perfetto_src_trace_processor_prelude_table_functions_tables",
+        "perfetto_src_trace_processor_prelude_tables_views_tables_views",
         "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
         "perfetto_src_trace_processor_tables_tables_python",
     ],
@@ -2268,6 +2287,48 @@
     test_config: "PerfettoIntegrationTests.xml",
 }
 
+// GN: //protos/perfetto/cloud_trace_processor:lite
+genrule {
+    name: "perfetto_protos_perfetto_cloud_trace_processor_lite_gen",
+    srcs: [
+        "protos/perfetto/cloud_trace_processor/common.proto",
+        "protos/perfetto/cloud_trace_processor/orchestrator.proto",
+        "protos/perfetto/cloud_trace_processor/worker.proto",
+    ],
+    tools: [
+        "aprotoc",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    out: [
+        "external/perfetto/protos/perfetto/cloud_trace_processor/common.pb.cc",
+        "external/perfetto/protos/perfetto/cloud_trace_processor/orchestrator.pb.cc",
+        "external/perfetto/protos/perfetto/cloud_trace_processor/worker.pb.cc",
+    ],
+}
+
+// GN: //protos/perfetto/cloud_trace_processor:lite
+genrule {
+    name: "perfetto_protos_perfetto_cloud_trace_processor_lite_gen_headers",
+    srcs: [
+        "protos/perfetto/cloud_trace_processor/common.proto",
+        "protos/perfetto/cloud_trace_processor/orchestrator.proto",
+        "protos/perfetto/cloud_trace_processor/worker.proto",
+    ],
+    tools: [
+        "aprotoc",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    out: [
+        "external/perfetto/protos/perfetto/cloud_trace_processor/common.pb.h",
+        "external/perfetto/protos/perfetto/cloud_trace_processor/orchestrator.pb.h",
+        "external/perfetto/protos/perfetto/cloud_trace_processor/worker.pb.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+    ],
+}
+
 // GN: //protos/perfetto/common:cpp
 genrule {
     name: "perfetto_protos_perfetto_common_cpp_gen",
@@ -6848,6 +6909,48 @@
     ],
 }
 
+// GN: //protos/perfetto/trace_processor:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_processor_lite_gen",
+    srcs: [
+        "protos/perfetto/trace_processor/metatrace_categories.proto",
+        "protos/perfetto/trace_processor/stack.proto",
+        "protos/perfetto/trace_processor/trace_processor.proto",
+    ],
+    tools: [
+        "aprotoc",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    out: [
+        "external/perfetto/protos/perfetto/trace_processor/metatrace_categories.pb.cc",
+        "external/perfetto/protos/perfetto/trace_processor/stack.pb.cc",
+        "external/perfetto/protos/perfetto/trace_processor/trace_processor.pb.cc",
+    ],
+}
+
+// GN: //protos/perfetto/trace_processor:lite
+genrule {
+    name: "perfetto_protos_perfetto_trace_processor_lite_gen_headers",
+    srcs: [
+        "protos/perfetto/trace_processor/metatrace_categories.proto",
+        "protos/perfetto/trace_processor/stack.proto",
+        "protos/perfetto/trace_processor/trace_processor.proto",
+    ],
+    tools: [
+        "aprotoc",
+    ],
+    cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --cpp_out=lite=true:$(genDir)/external/perfetto/ $(in)",
+    out: [
+        "external/perfetto/protos/perfetto/trace_processor/metatrace_categories.pb.h",
+        "external/perfetto/protos/perfetto/trace_processor/stack.pb.h",
+        "external/perfetto/protos/perfetto/trace_processor/trace_processor.pb.h",
+    ],
+    export_include_dirs: [
+        ".",
+        "protos",
+    ],
+}
+
 // GN: //protos/perfetto/trace_processor:metrics_impl_zero
 genrule {
     name: "perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen",
@@ -6888,7 +6991,6 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_processor_zero_gen",
     srcs: [
-        "protos/perfetto/trace_processor/cloud_trace_processor.proto",
         "protos/perfetto/trace_processor/metatrace_categories.proto",
         "protos/perfetto/trace_processor/stack.proto",
         "protos/perfetto/trace_processor/trace_processor.proto",
@@ -6899,7 +7001,6 @@
     ],
     cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
     out: [
-        "external/perfetto/protos/perfetto/trace_processor/cloud_trace_processor.pbzero.cc",
         "external/perfetto/protos/perfetto/trace_processor/metatrace_categories.pbzero.cc",
         "external/perfetto/protos/perfetto/trace_processor/stack.pbzero.cc",
         "external/perfetto/protos/perfetto/trace_processor/trace_processor.pbzero.cc",
@@ -6910,7 +7011,6 @@
 genrule {
     name: "perfetto_protos_perfetto_trace_processor_zero_gen_headers",
     srcs: [
-        "protos/perfetto/trace_processor/cloud_trace_processor.proto",
         "protos/perfetto/trace_processor/metatrace_categories.proto",
         "protos/perfetto/trace_processor/stack.proto",
         "protos/perfetto/trace_processor/trace_processor.proto",
@@ -6921,7 +7021,6 @@
     ],
     cmd: "mkdir -p $(genDir)/external/perfetto/ && $(location aprotoc) --proto_path=external/perfetto --plugin=protoc-gen-plugin=$(location protozero_plugin) --plugin_out=wrapper_namespace=pbzero:$(genDir)/external/perfetto/ $(in)",
     out: [
-        "external/perfetto/protos/perfetto/trace_processor/cloud_trace_processor.pbzero.h",
         "external/perfetto/protos/perfetto/trace_processor/metatrace_categories.pbzero.h",
         "external/perfetto/protos/perfetto/trace_processor/stack.pbzero.h",
         "external/perfetto/protos/perfetto/trace_processor/trace_processor.pbzero.h",
@@ -8374,6 +8473,24 @@
     ],
 }
 
+// GN: //src/cloud_trace_processor:sources
+filegroup {
+    name: "perfetto_src_cloud_trace_processor_sources",
+    srcs: [
+        "src/cloud_trace_processor/orchestrator_impl.cc",
+        "src/cloud_trace_processor/trace_processor_wrapper.cc",
+        "src/cloud_trace_processor/worker_impl.cc",
+    ],
+}
+
+// GN: //src/cloud_trace_processor:unittests
+filegroup {
+    name: "perfetto_src_cloud_trace_processor_unittests",
+    srcs: [
+        "src/cloud_trace_processor/trace_processor_wrapper_unittest.cc",
+    ],
+}
+
 // GN: //src/ipc:client
 filegroup {
     name: "perfetto_src_ipc_client",
@@ -9280,7 +9397,12 @@
     name: "perfetto_src_trace_processor_db_db",
     srcs: [
         "src/trace_processor/db/column.cc",
+        "src/trace_processor/db/column_overlay.cc",
         "src/trace_processor/db/column_storage.cc",
+        "src/trace_processor/db/null_overlay.cc",
+        "src/trace_processor/db/numeric_storage.cc",
+        "src/trace_processor/db/storage.cc",
+        "src/trace_processor/db/storage_overlay.cc",
         "src/trace_processor/db/table.cc",
         "src/trace_processor/db/view.cc",
     ],
@@ -9292,11 +9414,39 @@
     srcs: [
         "src/trace_processor/db/column_storage_overlay_unittest.cc",
         "src/trace_processor/db/compare_unittest.cc",
-        "src/trace_processor/db/table_unittest.cc",
+        "src/trace_processor/db/storage_unittest.cc",
         "src/trace_processor/db/view_unittest.cc",
     ],
 }
 
+// GN: //src/trace_processor/db:view_unittest
+genrule {
+    name: "perfetto_src_trace_processor_db_view_unittest",
+    srcs: [
+        "src/trace_processor/db/view_unittest.py",
+    ],
+    tools: [
+        "perfetto_src_trace_processor_db_view_unittest_binary",
+    ],
+    cmd: "$(location perfetto_src_trace_processor_db_view_unittest_binary) --gen-dir=$(genDir) --relative-input-dir=external/perfetto --inputs $(in)",
+    out: [
+        "src/trace_processor/db/view_unittest_py.h",
+    ],
+}
+
+// GN: //src/trace_processor/db:view_unittest
+python_binary_host {
+    name: "perfetto_src_trace_processor_db_view_unittest_binary",
+    srcs: [
+        "python/generators/trace_processor_table/public.py",
+        "python/generators/trace_processor_table/serialize.py",
+        "python/generators/trace_processor_table/util.py",
+        "src/trace_processor/db/view_unittest.py",
+        "tools/gen_tp_table_headers.py",
+    ],
+    main: "tools/gen_tp_table_headers.py",
+}
+
 // GN: //src/trace_processor:demangle
 cc_library_static {
     name: "perfetto_src_trace_processor_demangle",
@@ -9890,6 +10040,7 @@
         "src/trace_processor/metrics/sql/android/java_heap_histogram.sql",
         "src/trace_processor/metrics/sql/android/java_heap_stats.sql",
         "src/trace_processor/metrics/sql/android/mem_stats_priority_breakdown.sql",
+        "src/trace_processor/metrics/sql/android/network_activity_template.sql",
         "src/trace_processor/metrics/sql/android/p_state.sql",
         "src/trace_processor/metrics/sql/android/power_drain_in_watts.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data.sql",
@@ -10019,9 +10170,17 @@
         "src/trace_processor/prelude/functions/import.cc",
         "src/trace_processor/prelude/functions/layout_functions.cc",
         "src/trace_processor/prelude/functions/pprof_functions.cc",
-        "src/trace_processor/prelude/functions/register_function.cc",
         "src/trace_processor/prelude/functions/sqlite3_str_split.cc",
         "src/trace_processor/prelude/functions/stack_functions.cc",
+        "src/trace_processor/prelude/functions/to_ftrace.cc",
+    ],
+}
+
+// GN: //src/trace_processor/prelude/functions:interface
+filegroup {
+    name: "perfetto_src_trace_processor_prelude_functions_interface",
+    srcs: [
+        "src/trace_processor/prelude/functions/sql_function.cc",
     ],
 }
 
@@ -10050,6 +10209,14 @@
     ],
 }
 
+// GN: //src/trace_processor/prelude/table_functions:interface
+filegroup {
+    name: "perfetto_src_trace_processor_prelude_table_functions_interface",
+    srcs: [
+        "src/trace_processor/prelude/table_functions/table_function.cc",
+    ],
+}
+
 // GN: //src/trace_processor/prelude/table_functions:table_functions
 filegroup {
     name: "perfetto_src_trace_processor_prelude_table_functions_table_functions",
@@ -10064,11 +10231,47 @@
         "src/trace_processor/prelude/table_functions/experimental_sched_upid.cc",
         "src/trace_processor/prelude/table_functions/experimental_slice_layout.cc",
         "src/trace_processor/prelude/table_functions/flamegraph_construction_algorithms.cc",
-        "src/trace_processor/prelude/table_functions/table_function.cc",
         "src/trace_processor/prelude/table_functions/view.cc",
     ],
 }
 
+// GN: //src/trace_processor/prelude/table_functions:tables
+genrule {
+    name: "perfetto_src_trace_processor_prelude_table_functions_tables",
+    srcs: [
+        "src/trace_processor/prelude/table_functions/tables.py",
+    ],
+    tools: [
+        "perfetto_src_trace_processor_prelude_table_functions_tables_binary",
+    ],
+    cmd: "$(location perfetto_src_trace_processor_prelude_table_functions_tables_binary) --gen-dir=$(genDir) --relative-input-dir=external/perfetto --inputs $(in)",
+    out: [
+        "src/trace_processor/prelude/table_functions/tables_py.h",
+    ],
+}
+
+// GN: //src/trace_processor/prelude/table_functions:tables
+python_binary_host {
+    name: "perfetto_src_trace_processor_prelude_table_functions_tables_binary",
+    srcs: [
+        "python/generators/trace_processor_table/public.py",
+        "python/generators/trace_processor_table/serialize.py",
+        "python/generators/trace_processor_table/util.py",
+        "src/trace_processor/prelude/table_functions/tables.py",
+        "src/trace_processor/tables/android_tables.py",
+        "src/trace_processor/tables/counter_tables.py",
+        "src/trace_processor/tables/flow_tables.py",
+        "src/trace_processor/tables/memory_tables.py",
+        "src/trace_processor/tables/metadata_tables.py",
+        "src/trace_processor/tables/profiler_tables.py",
+        "src/trace_processor/tables/slice_tables.py",
+        "src/trace_processor/tables/trace_proto_tables.py",
+        "src/trace_processor/tables/track_tables.py",
+        "tools/gen_tp_table_headers.py",
+    ],
+    main: "tools/gen_tp_table_headers.py",
+}
+
 // GN: //src/trace_processor/prelude/table_functions:unittests
 filegroup {
     name: "perfetto_src_trace_processor_prelude_table_functions_unittests",
@@ -10082,6 +10285,22 @@
     ],
 }
 
+// GN: //src/trace_processor/prelude/tables_views:tables_views
+genrule {
+    name: "perfetto_src_trace_processor_prelude_tables_views_tables_views",
+    srcs: [
+        "src/trace_processor/prelude/tables_views/tables.sql",
+        "src/trace_processor/prelude/tables_views/views.sql",
+    ],
+    cmd: "$(location tools/gen_amalgamated_sql.py) --namespace=prelude::tables_views --cpp-out=$(out) $(in)",
+    out: [
+        "src/trace_processor/prelude/tables_views/tables_views.h",
+    ],
+    tool_files: [
+        "tools/gen_amalgamated_sql.py",
+    ],
+}
+
 // GN: //src/trace_processor/rpc:httpd
 filegroup {
     name: "perfetto_src_trace_processor_rpc_httpd",
@@ -10125,27 +10344,27 @@
     ],
 }
 
+// GN: //src/trace_processor/sqlite:query_constraints
+filegroup {
+    name: "perfetto_src_trace_processor_sqlite_query_constraints",
+    srcs: [
+        "src/trace_processor/sqlite/query_constraints.cc",
+    ],
+}
+
 // GN: //src/trace_processor/sqlite:sqlite
 filegroup {
     name: "perfetto_src_trace_processor_sqlite_sqlite",
     srcs: [
         "src/trace_processor/sqlite/db_sqlite_table.cc",
         "src/trace_processor/sqlite/sql_stats_table.cc",
-        "src/trace_processor/sqlite/sqlite_raw_table.cc",
+        "src/trace_processor/sqlite/sqlite_engine.cc",
+        "src/trace_processor/sqlite/sqlite_table.cc",
         "src/trace_processor/sqlite/sqlite_utils.cc",
         "src/trace_processor/sqlite/stats_table.cc",
     ],
 }
 
-// GN: //src/trace_processor/sqlite:sqlite_minimal
-filegroup {
-    name: "perfetto_src_trace_processor_sqlite_sqlite_minimal",
-    srcs: [
-        "src/trace_processor/sqlite/query_constraints.cc",
-        "src/trace_processor/sqlite/sqlite_table.cc",
-    ],
-}
-
 // GN: //src/trace_processor/sqlite:unittests
 filegroup {
     name: "perfetto_src_trace_processor_sqlite_unittests",
@@ -10161,16 +10380,21 @@
     name: "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
     srcs: [
         "src/trace_processor/stdlib/android/battery.sql",
+        "src/trace_processor/stdlib/android/battery_stats.sql",
         "src/trace_processor/stdlib/android/binder.sql",
         "src/trace_processor/stdlib/android/monitor_contention.sql",
+        "src/trace_processor/stdlib/android/network_packets.sql",
         "src/trace_processor/stdlib/android/process_metadata.sql",
         "src/trace_processor/stdlib/android/slices.sql",
         "src/trace_processor/stdlib/android/startup/internal_startups_maxsdk28.sql",
         "src/trace_processor/stdlib/android/startup/internal_startups_minsdk29.sql",
         "src/trace_processor/stdlib/android/startup/internal_startups_minsdk33.sql",
         "src/trace_processor/stdlib/android/startup/startups.sql",
+        "src/trace_processor/stdlib/android/statsd.sql",
+        "src/trace_processor/stdlib/chrome/chrome_scrolls.sql",
         "src/trace_processor/stdlib/chrome/cpu_powerups.sql",
         "src/trace_processor/stdlib/common/counters.sql",
+        "src/trace_processor/stdlib/common/cpus.sql",
         "src/trace_processor/stdlib/common/metadata.sql",
         "src/trace_processor/stdlib/common/percentiles.sql",
         "src/trace_processor/stdlib/common/slices.sql",
@@ -10218,7 +10442,7 @@
     tools: [
         "perfetto_src_trace_processor_tables_py_tables_unittest_binary",
     ],
-    cmd: "$(location perfetto_src_trace_processor_tables_py_tables_unittest_binary) --gen-dir=$(genDir) --inputs $(in) --outputs $(out)",
+    cmd: "$(location perfetto_src_trace_processor_tables_py_tables_unittest_binary) --gen-dir=$(genDir) --relative-input-dir=external/perfetto --inputs $(in)",
     out: [
         "src/trace_processor/tables/py_tables_unittest_py.h",
     ],
@@ -10262,7 +10486,7 @@
     tools: [
         "perfetto_src_trace_processor_tables_tables_python_binary",
     ],
-    cmd: "$(location perfetto_src_trace_processor_tables_tables_python_binary) --gen-dir=$(genDir) --inputs $(in) --outputs $(out)",
+    cmd: "$(location perfetto_src_trace_processor_tables_tables_python_binary) --gen-dir=$(genDir) --relative-input-dir=external/perfetto --inputs $(in)",
     out: [
         "src/trace_processor/tables/android_tables_py.h",
         "src/trace_processor/tables/counter_tables_py.h",
@@ -10301,7 +10525,6 @@
 filegroup {
     name: "perfetto_src_trace_processor_tables_unittests",
     srcs: [
-        "src/trace_processor/tables/macros_unittest.cc",
         "src/trace_processor/tables/py_tables_unittest.cc",
     ],
 }
@@ -10322,7 +10545,6 @@
         "src/trace_processor/types/destructible.cc",
         "src/trace_processor/types/gfp_flags.cc",
         "src/trace_processor/types/task_state.cc",
-        "src/trace_processor/types/variadic.cc",
     ],
 }
 
@@ -10462,6 +10684,34 @@
     ],
 }
 
+// GN: //src/trace_processor/views:macros_unittest
+genrule {
+    name: "perfetto_src_trace_processor_views_macros_unittest",
+    srcs: [
+        "src/trace_processor/views/macros_unittest.py",
+    ],
+    tools: [
+        "perfetto_src_trace_processor_views_macros_unittest_binary",
+    ],
+    cmd: "$(location perfetto_src_trace_processor_views_macros_unittest_binary) --gen-dir=$(genDir) --relative-input-dir=external/perfetto --inputs $(in)",
+    out: [
+        "src/trace_processor/views/macros_unittest_py.h",
+    ],
+}
+
+// GN: //src/trace_processor/views:macros_unittest
+python_binary_host {
+    name: "perfetto_src_trace_processor_views_macros_unittest_binary",
+    srcs: [
+        "python/generators/trace_processor_table/public.py",
+        "python/generators/trace_processor_table/serialize.py",
+        "python/generators/trace_processor_table/util.py",
+        "src/trace_processor/views/macros_unittest.py",
+        "tools/gen_tp_table_headers.py",
+    ],
+    main: "tools/gen_tp_table_headers.py",
+}
+
 // GN: //src/trace_processor/views:unittests
 filegroup {
     name: "perfetto_src_trace_processor_views_unittests",
@@ -10523,6 +10773,14 @@
     ],
 }
 
+// GN: //src/traceconv:unittests
+filegroup {
+    name: "perfetto_src_traceconv_unittests",
+    srcs: [
+        "src/traceconv/trace_to_text_unittest.cc",
+    ],
+}
+
 // GN: //src/traceconv:utils
 filegroup {
     name: "perfetto_src_traceconv_utils",
@@ -11077,6 +11335,15 @@
         "src/tracing/core/trace_packet_unittest.cc",
         "src/tracing/core/trace_writer_impl_unittest.cc",
         "src/tracing/core/tracing_service_impl_unittest.cc",
+        "src/tracing/core/zlib_compressor_unittest.cc",
+    ],
+}
+
+// GN: //src/tracing/core:zlib_compressor
+filegroup {
+    name: "perfetto_src_tracing_core_zlib_compressor",
+    srcs: [
+        "src/tracing/core/zlib_compressor.cc",
     ],
 }
 
@@ -11572,6 +11839,7 @@
         ":perfetto_include_perfetto_ext_base_http_http",
         ":perfetto_include_perfetto_ext_base_threading_threading",
         ":perfetto_include_perfetto_ext_base_version",
+        ":perfetto_include_perfetto_ext_cloud_trace_processor_cloud_trace_processor",
         ":perfetto_include_perfetto_ext_ipc_ipc",
         ":perfetto_include_perfetto_ext_trace_processor_demangle",
         ":perfetto_include_perfetto_ext_trace_processor_export_json",
@@ -11580,6 +11848,7 @@
         ":perfetto_include_perfetto_ext_traced_traced",
         ":perfetto_include_perfetto_ext_tracing_core_core",
         ":perfetto_include_perfetto_ext_tracing_ipc_ipc",
+        ":perfetto_include_perfetto_profiling_pprof_builder",
         ":perfetto_include_perfetto_protozero_protozero",
         ":perfetto_include_perfetto_public_abi_base",
         ":perfetto_include_perfetto_public_base",
@@ -11591,6 +11860,7 @@
         ":perfetto_include_perfetto_tracing_core_core",
         ":perfetto_include_perfetto_tracing_core_forward_decls",
         ":perfetto_include_perfetto_tracing_tracing",
+        ":perfetto_protos_perfetto_cloud_trace_processor_lite_gen",
         ":perfetto_protos_perfetto_common_cpp_gen",
         ":perfetto_protos_perfetto_common_lite_gen",
         ":perfetto_protos_perfetto_common_zero_gen",
@@ -11666,6 +11936,7 @@
         ":perfetto_protos_perfetto_trace_power_cpp_gen",
         ":perfetto_protos_perfetto_trace_power_lite_gen",
         ":perfetto_protos_perfetto_trace_power_zero_gen",
+        ":perfetto_protos_perfetto_trace_processor_lite_gen",
         ":perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen",
         ":perfetto_protos_perfetto_trace_processor_zero_gen",
         ":perfetto_protos_perfetto_trace_profiling_cpp_gen",
@@ -11704,6 +11975,8 @@
         ":perfetto_src_base_unittests",
         ":perfetto_src_base_unix_socket",
         ":perfetto_src_base_version",
+        ":perfetto_src_cloud_trace_processor_sources",
+        ":perfetto_src_cloud_trace_processor_unittests",
         ":perfetto_src_ipc_client",
         ":perfetto_src_ipc_common",
         ":perfetto_src_ipc_host",
@@ -11741,6 +12014,7 @@
         ":perfetto_src_profiling_perf_producer_unittests",
         ":perfetto_src_profiling_perf_regs_parsing",
         ":perfetto_src_profiling_perf_unwinding",
+        ":perfetto_src_profiling_symbolizer_symbolize_database",
         ":perfetto_src_profiling_symbolizer_symbolizer",
         ":perfetto_src_profiling_symbolizer_unittests",
         ":perfetto_src_profiling_unittests",
@@ -11798,17 +12072,19 @@
         ":perfetto_src_trace_processor_metrics_metrics",
         ":perfetto_src_trace_processor_metrics_unittests",
         ":perfetto_src_trace_processor_prelude_functions_functions",
+        ":perfetto_src_trace_processor_prelude_functions_interface",
         ":perfetto_src_trace_processor_prelude_functions_unittests",
         ":perfetto_src_trace_processor_prelude_operators_operators",
         ":perfetto_src_trace_processor_prelude_operators_unittests",
+        ":perfetto_src_trace_processor_prelude_table_functions_interface",
         ":perfetto_src_trace_processor_prelude_table_functions_table_functions",
         ":perfetto_src_trace_processor_prelude_table_functions_unittests",
         ":perfetto_src_trace_processor_rpc_rpc",
         ":perfetto_src_trace_processor_rpc_unittests",
         ":perfetto_src_trace_processor_sorter_sorter",
         ":perfetto_src_trace_processor_sorter_unittests",
+        ":perfetto_src_trace_processor_sqlite_query_constraints",
         ":perfetto_src_trace_processor_sqlite_sqlite",
-        ":perfetto_src_trace_processor_sqlite_sqlite_minimal",
         ":perfetto_src_trace_processor_sqlite_unittests",
         ":perfetto_src_trace_processor_storage_minimal",
         ":perfetto_src_trace_processor_storage_storage",
@@ -11835,6 +12111,10 @@
         ":perfetto_src_trace_processor_util_zip_reader",
         ":perfetto_src_trace_processor_views_unittests",
         ":perfetto_src_trace_processor_views_views",
+        ":perfetto_src_traceconv_lib",
+        ":perfetto_src_traceconv_pprofbuilder",
+        ":perfetto_src_traceconv_unittests",
+        ":perfetto_src_traceconv_utils",
         ":perfetto_src_traced_probes_android_game_intervention_list_android_game_intervention_list",
         ":perfetto_src_traced_probes_android_game_intervention_list_unittests",
         ":perfetto_src_traced_probes_android_log_android_log",
@@ -11882,6 +12162,7 @@
         ":perfetto_src_tracing_core_service",
         ":perfetto_src_tracing_core_test_support",
         ":perfetto_src_tracing_core_unittests",
+        ":perfetto_src_tracing_core_zlib_compressor",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_consumer_consumer",
         ":perfetto_src_tracing_ipc_default_socket",
@@ -11915,6 +12196,7 @@
         "perfetto_gtest_logcat_printer",
     ],
     generated_headers: [
+        "perfetto_protos_perfetto_cloud_trace_processor_lite_gen_headers",
         "perfetto_protos_perfetto_common_cpp_gen_headers",
         "perfetto_protos_perfetto_common_lite_gen_headers",
         "perfetto_protos_perfetto_common_zero_gen_headers",
@@ -11990,6 +12272,7 @@
         "perfetto_protos_perfetto_trace_power_cpp_gen_headers",
         "perfetto_protos_perfetto_trace_power_lite_gen_headers",
         "perfetto_protos_perfetto_trace_power_zero_gen_headers",
+        "perfetto_protos_perfetto_trace_processor_lite_gen_headers",
         "perfetto_protos_perfetto_trace_processor_metrics_impl_zero_gen_headers",
         "perfetto_protos_perfetto_trace_processor_zero_gen_headers",
         "perfetto_protos_perfetto_trace_profiling_cpp_gen_headers",
@@ -12023,6 +12306,7 @@
         "perfetto_src_protozero_testing_messages_cpp_gen_headers",
         "perfetto_src_protozero_testing_messages_lite_gen_headers",
         "perfetto_src_protozero_testing_messages_zero_gen_headers",
+        "perfetto_src_trace_processor_db_view_unittest",
         "perfetto_src_trace_processor_gen_cc_test_messages_descriptor",
         "perfetto_src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
         "perfetto_src_trace_processor_importers_proto_gen_cc_config_descriptor",
@@ -12033,9 +12317,13 @@
         "perfetto_src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+        "perfetto_src_trace_processor_prelude_table_functions_tables",
+        "perfetto_src_trace_processor_prelude_tables_views_tables_views",
         "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
         "perfetto_src_trace_processor_tables_py_tables_unittest",
         "perfetto_src_trace_processor_tables_tables_python",
+        "perfetto_src_trace_processor_views_macros_unittest",
+        "perfetto_src_traceconv_gen_cc_trace_descriptor",
         "perfetto_src_traced_probes_ftrace_test_messages_cpp_gen_headers",
         "perfetto_src_traced_probes_ftrace_test_messages_lite_gen_headers",
         "perfetto_src_traced_probes_ftrace_test_messages_zero_gen_headers",
@@ -12471,13 +12759,15 @@
         ":perfetto_src_trace_processor_metatrace",
         ":perfetto_src_trace_processor_metrics_metrics",
         ":perfetto_src_trace_processor_prelude_functions_functions",
+        ":perfetto_src_trace_processor_prelude_functions_interface",
         ":perfetto_src_trace_processor_prelude_operators_operators",
+        ":perfetto_src_trace_processor_prelude_table_functions_interface",
         ":perfetto_src_trace_processor_prelude_table_functions_table_functions",
         ":perfetto_src_trace_processor_rpc_httpd",
         ":perfetto_src_trace_processor_rpc_rpc",
         ":perfetto_src_trace_processor_sorter_sorter",
+        ":perfetto_src_trace_processor_sqlite_query_constraints",
         ":perfetto_src_trace_processor_sqlite_sqlite",
-        ":perfetto_src_trace_processor_sqlite_sqlite_minimal",
         ":perfetto_src_trace_processor_storage_minimal",
         ":perfetto_src_trace_processor_storage_storage",
         ":perfetto_src_trace_processor_tables_tables",
@@ -12549,6 +12839,8 @@
         "perfetto_src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+        "perfetto_src_trace_processor_prelude_table_functions_tables",
+        "perfetto_src_trace_processor_prelude_tables_views_tables_views",
         "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
         "perfetto_src_trace_processor_tables_tables_python",
     ],
@@ -12692,11 +12984,13 @@
         ":perfetto_src_trace_processor_metatrace",
         ":perfetto_src_trace_processor_metrics_metrics",
         ":perfetto_src_trace_processor_prelude_functions_functions",
+        ":perfetto_src_trace_processor_prelude_functions_interface",
         ":perfetto_src_trace_processor_prelude_operators_operators",
+        ":perfetto_src_trace_processor_prelude_table_functions_interface",
         ":perfetto_src_trace_processor_prelude_table_functions_table_functions",
         ":perfetto_src_trace_processor_sorter_sorter",
+        ":perfetto_src_trace_processor_sqlite_query_constraints",
         ":perfetto_src_trace_processor_sqlite_sqlite",
-        ":perfetto_src_trace_processor_sqlite_sqlite_minimal",
         ":perfetto_src_trace_processor_storage_minimal",
         ":perfetto_src_trace_processor_storage_storage",
         ":perfetto_src_trace_processor_tables_tables",
@@ -12772,6 +13066,8 @@
         "perfetto_src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_gen_cc_metrics_descriptor",
         "perfetto_src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+        "perfetto_src_trace_processor_prelude_table_functions_tables",
+        "perfetto_src_trace_processor_prelude_tables_views_tables_views",
         "perfetto_src_trace_processor_stdlib_gen_amalgamated_stdlib",
         "perfetto_src_trace_processor_tables_tables_python",
         "perfetto_src_traceconv_gen_cc_trace_descriptor",
diff --git a/BUILD b/BUILD
index 01153ff..195292f 100644
--- a/BUILD
+++ b/BUILD
@@ -64,6 +64,152 @@
     linkstatic = True,
 )
 
+# GN target: //src/cloud_trace_processor:cloud_trace_processor
+perfetto_cc_library(
+    name = "cloud_trace_processor",
+    srcs = [
+        ":src_base_threading_threading",
+        ":src_cloud_trace_processor_sources",
+        ":src_kernel_utils_syscall_table",
+        ":src_protozero_proto_ring_buffer",
+        ":src_trace_processor_db_db",
+        ":src_trace_processor_export_json",
+        ":src_trace_processor_importers_android_bugreport_android_bugreport",
+        ":src_trace_processor_importers_common_common",
+        ":src_trace_processor_importers_common_parser_types",
+        ":src_trace_processor_importers_common_trace_parser_hdr",
+        ":src_trace_processor_importers_ftrace_ftrace_descriptors",
+        ":src_trace_processor_importers_ftrace_full",
+        ":src_trace_processor_importers_ftrace_minimal",
+        ":src_trace_processor_importers_fuchsia_fuchsia_record",
+        ":src_trace_processor_importers_fuchsia_full",
+        ":src_trace_processor_importers_fuchsia_minimal",
+        ":src_trace_processor_importers_gzip_full",
+        ":src_trace_processor_importers_i2c_full",
+        ":src_trace_processor_importers_json_full",
+        ":src_trace_processor_importers_json_minimal",
+        ":src_trace_processor_importers_memory_tracker_graph_processor",
+        ":src_trace_processor_importers_ninja_ninja",
+        ":src_trace_processor_importers_proto_full",
+        ":src_trace_processor_importers_proto_minimal",
+        ":src_trace_processor_importers_proto_packet_sequence_state_generation_hdr",
+        ":src_trace_processor_importers_proto_proto_importer_module",
+        ":src_trace_processor_importers_syscalls_full",
+        ":src_trace_processor_importers_systrace_full",
+        ":src_trace_processor_importers_systrace_systrace_line",
+        ":src_trace_processor_importers_systrace_systrace_parser",
+        ":src_trace_processor_lib",
+        ":src_trace_processor_metatrace",
+        ":src_trace_processor_metrics_metrics",
+        ":src_trace_processor_prelude_functions_functions",
+        ":src_trace_processor_prelude_functions_interface",
+        ":src_trace_processor_prelude_operators_operators",
+        ":src_trace_processor_prelude_table_functions_interface",
+        ":src_trace_processor_prelude_table_functions_table_functions",
+        ":src_trace_processor_prelude_table_functions_tables",
+        ":src_trace_processor_rpc_rpc",
+        ":src_trace_processor_sorter_sorter",
+        ":src_trace_processor_sqlite_query_constraints",
+        ":src_trace_processor_sqlite_sqlite",
+        ":src_trace_processor_storage_minimal",
+        ":src_trace_processor_storage_storage",
+        ":src_trace_processor_tables_tables",
+        ":src_trace_processor_tables_tables_python",
+        ":src_trace_processor_types_types",
+        ":src_trace_processor_util_bump_allocator",
+        ":src_trace_processor_util_descriptors",
+        ":src_trace_processor_util_glob",
+        ":src_trace_processor_util_gzip",
+        ":src_trace_processor_util_interned_message_view",
+        ":src_trace_processor_util_profile_builder",
+        ":src_trace_processor_util_proto_profiler",
+        ":src_trace_processor_util_proto_to_args_parser",
+        ":src_trace_processor_util_protozero_to_text",
+        ":src_trace_processor_util_sql_argument",
+        ":src_trace_processor_util_stack_traces_util",
+        ":src_trace_processor_util_stdlib",
+        ":src_trace_processor_util_util",
+        ":src_trace_processor_util_zip_reader",
+        ":src_trace_processor_views_views",
+    ],
+    hdrs = [
+        ":include_perfetto_base_base",
+        ":include_perfetto_ext_base_base",
+        ":include_perfetto_ext_base_threading_threading",
+        ":include_perfetto_ext_cloud_trace_processor_cloud_trace_processor",
+        ":include_perfetto_ext_trace_processor_demangle",
+        ":include_perfetto_ext_trace_processor_export_json",
+        ":include_perfetto_ext_trace_processor_importers_memory_tracker_memory_tracker",
+        ":include_perfetto_ext_traced_sys_stats_counters",
+        ":include_perfetto_protozero_protozero",
+        ":include_perfetto_public_abi_base",
+        ":include_perfetto_public_base",
+        ":include_perfetto_public_protozero",
+        ":include_perfetto_trace_processor_basic_types",
+        ":include_perfetto_trace_processor_storage",
+        ":include_perfetto_trace_processor_trace_processor",
+    ],
+    deps = [
+               ":protos_perfetto_cloud_trace_processor_lite",
+               ":protos_perfetto_common_lite",
+               ":protos_perfetto_common_zero",
+               ":protos_perfetto_config_android_zero",
+               ":protos_perfetto_config_ftrace_zero",
+               ":protos_perfetto_config_gpu_zero",
+               ":protos_perfetto_config_inode_file_zero",
+               ":protos_perfetto_config_interceptors_zero",
+               ":protos_perfetto_config_power_zero",
+               ":protos_perfetto_config_process_stats_zero",
+               ":protos_perfetto_config_profiling_zero",
+               ":protos_perfetto_config_statsd_zero",
+               ":protos_perfetto_config_sys_stats_zero",
+               ":protos_perfetto_config_system_info_zero",
+               ":protos_perfetto_config_track_event_zero",
+               ":protos_perfetto_config_zero",
+               ":protos_perfetto_trace_android_zero",
+               ":protos_perfetto_trace_chrome_zero",
+               ":protos_perfetto_trace_filesystem_zero",
+               ":protos_perfetto_trace_ftrace_zero",
+               ":protos_perfetto_trace_gpu_zero",
+               ":protos_perfetto_trace_interned_data_zero",
+               ":protos_perfetto_trace_minimal_zero",
+               ":protos_perfetto_trace_non_minimal_zero",
+               ":protos_perfetto_trace_perfetto_zero",
+               ":protos_perfetto_trace_power_zero",
+               ":protos_perfetto_trace_processor_lite",
+               ":protos_perfetto_trace_processor_metrics_impl_zero",
+               ":protos_perfetto_trace_processor_zero",
+               ":protos_perfetto_trace_profiling_zero",
+               ":protos_perfetto_trace_ps_zero",
+               ":protos_perfetto_trace_statsd_zero",
+               ":protos_perfetto_trace_sys_stats_zero",
+               ":protos_perfetto_trace_system_info_zero",
+               ":protos_perfetto_trace_track_event_zero",
+               ":protos_perfetto_trace_translation_zero",
+               ":protos_third_party_pprof_zero",
+               ":protozero",
+               ":src_base_base",
+               ":src_base_version",
+               ":src_trace_processor_containers_containers",
+               ":src_trace_processor_importers_proto_gen_cc_chrome_track_event_descriptor",
+               ":src_trace_processor_importers_proto_gen_cc_config_descriptor",
+               ":src_trace_processor_importers_proto_gen_cc_statsd_atoms_descriptor",
+               ":src_trace_processor_importers_proto_gen_cc_trace_descriptor",
+               ":src_trace_processor_importers_proto_gen_cc_track_event_descriptor",
+               ":src_trace_processor_metrics_gen_cc_all_chrome_metrics_descriptor",
+               ":src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
+               ":src_trace_processor_metrics_gen_cc_metrics_descriptor",
+               ":src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+               ":src_trace_processor_prelude_tables_views_tables_views",
+               ":src_trace_processor_stdlib_gen_amalgamated_stdlib",
+           ] + PERFETTO_CONFIG.deps.jsoncpp +
+           PERFETTO_CONFIG.deps.sqlite +
+           PERFETTO_CONFIG.deps.sqlite_ext_percentile +
+           PERFETTO_CONFIG.deps.zlib +
+           PERFETTO_CONFIG.deps.demangle_wrapper,
+    linkstatic = True,
+)
+
 # GN target: //src/ipc/protoc_plugin:ipc_plugin
 perfetto_cc_binary(
     name = "ipc_plugin",
@@ -279,6 +425,7 @@
         ":src_tracing_common",
         ":src_tracing_core_core",
         ":src_tracing_core_service",
+        ":src_tracing_core_zlib_compressor",
         ":src_tracing_ipc_common",
         ":src_tracing_ipc_default_socket",
         ":src_tracing_ipc_producer_producer",
@@ -355,7 +502,7 @@
         ":protozero",
         ":src_base_base",
         ":src_base_version",
-    ],
+    ] + PERFETTO_CONFIG.deps.zlib,
     linkstatic = True,
 )
 
@@ -387,6 +534,22 @@
     ],
 )
 
+# GN target: //include/perfetto/ext/base/threading:threading
+perfetto_filegroup(
+    name = "include_perfetto_ext_base_threading_threading",
+    srcs = [
+        "include/perfetto/ext/base/threading/channel.h",
+        "include/perfetto/ext/base/threading/future.h",
+        "include/perfetto/ext/base/threading/future_combinators.h",
+        "include/perfetto/ext/base/threading/poll.h",
+        "include/perfetto/ext/base/threading/spawn.h",
+        "include/perfetto/ext/base/threading/stream.h",
+        "include/perfetto/ext/base/threading/stream_combinators.h",
+        "include/perfetto/ext/base/threading/thread_pool.h",
+        "include/perfetto/ext/base/threading/util.h",
+    ],
+)
+
 # GN target: //include/perfetto/ext/base:base
 perfetto_filegroup(
     name = "include_perfetto_ext_base_base",
@@ -446,6 +609,16 @@
     ],
 )
 
+# GN target: //include/perfetto/ext/cloud_trace_processor:cloud_trace_processor
+perfetto_filegroup(
+    name = "include_perfetto_ext_cloud_trace_processor_cloud_trace_processor",
+    srcs = [
+        "include/perfetto/ext/cloud_trace_processor/environment.h",
+        "include/perfetto/ext/cloud_trace_processor/orchestrator.h",
+        "include/perfetto/ext/cloud_trace_processor/worker.h",
+    ],
+)
+
 # GN target: //include/perfetto/ext/ipc:ipc
 perfetto_filegroup(
     name = "include_perfetto_ext_ipc_ipc",
@@ -758,6 +931,16 @@
     linkstatic = True,
 )
 
+# GN target: //src/base/threading:threading
+perfetto_filegroup(
+    name = "src_base_threading_threading",
+    srcs = [
+        "src/base/threading/spawn.cc",
+        "src/base/threading/stream_combinators.cc",
+        "src/base/threading/thread_pool.cc",
+    ],
+)
+
 # GN target: //src/base:base
 perfetto_cc_library(
     name = "src_base_base",
@@ -849,6 +1032,19 @@
     ],
 )
 
+# GN target: //src/cloud_trace_processor:sources
+perfetto_filegroup(
+    name = "src_cloud_trace_processor_sources",
+    srcs = [
+        "src/cloud_trace_processor/orchestrator_impl.cc",
+        "src/cloud_trace_processor/orchestrator_impl.h",
+        "src/cloud_trace_processor/trace_processor_wrapper.cc",
+        "src/cloud_trace_processor/trace_processor_wrapper.h",
+        "src/cloud_trace_processor/worker_impl.cc",
+        "src/cloud_trace_processor/worker_impl.h",
+    ],
+)
+
 # GN target: //src/ipc:client
 perfetto_filegroup(
     name = "src_ipc_client",
@@ -1082,10 +1278,22 @@
         "src/trace_processor/db/base_id.h",
         "src/trace_processor/db/column.cc",
         "src/trace_processor/db/column.h",
+        "src/trace_processor/db/column_overlay.cc",
+        "src/trace_processor/db/column_overlay.h",
         "src/trace_processor/db/column_storage.cc",
         "src/trace_processor/db/column_storage.h",
         "src/trace_processor/db/column_storage_overlay.h",
         "src/trace_processor/db/compare.h",
+        "src/trace_processor/db/null_overlay.cc",
+        "src/trace_processor/db/null_overlay.h",
+        "src/trace_processor/db/numeric_storage.cc",
+        "src/trace_processor/db/numeric_storage.h",
+        "src/trace_processor/db/sorting_overlay.h",
+        "src/trace_processor/db/storage.cc",
+        "src/trace_processor/db/storage.h",
+        "src/trace_processor/db/storage_overlay.cc",
+        "src/trace_processor/db/storage_overlay.h",
+        "src/trace_processor/db/storage_variants.h",
         "src/trace_processor/db/table.cc",
         "src/trace_processor/db/table.h",
         "src/trace_processor/db/typed_column.h",
@@ -1579,6 +1787,7 @@
         "src/trace_processor/metrics/sql/android/java_heap_histogram.sql",
         "src/trace_processor/metrics/sql/android/java_heap_stats.sql",
         "src/trace_processor/metrics/sql/android/mem_stats_priority_breakdown.sql",
+        "src/trace_processor/metrics/sql/android/network_activity_template.sql",
         "src/trace_processor/metrics/sql/android/p_state.sql",
         "src/trace_processor/metrics/sql/android/power_drain_in_watts.sql",
         "src/trace_processor/metrics/sql/android/power_profile_data.sql",
@@ -1794,17 +2003,26 @@
         "src/trace_processor/prelude/functions/layout_functions.h",
         "src/trace_processor/prelude/functions/pprof_functions.cc",
         "src/trace_processor/prelude/functions/pprof_functions.h",
-        "src/trace_processor/prelude/functions/register_function.cc",
-        "src/trace_processor/prelude/functions/register_function.h",
         "src/trace_processor/prelude/functions/sqlite3_str_split.cc",
         "src/trace_processor/prelude/functions/sqlite3_str_split.h",
         "src/trace_processor/prelude/functions/stack_functions.cc",
         "src/trace_processor/prelude/functions/stack_functions.h",
+        "src/trace_processor/prelude/functions/to_ftrace.cc",
+        "src/trace_processor/prelude/functions/to_ftrace.h",
         "src/trace_processor/prelude/functions/utils.h",
         "src/trace_processor/prelude/functions/window_functions.h",
     ],
 )
 
+# GN target: //src/trace_processor/prelude/functions:interface
+perfetto_filegroup(
+    name = "src_trace_processor_prelude_functions_interface",
+    srcs = [
+        "src/trace_processor/prelude/functions/sql_function.cc",
+        "src/trace_processor/prelude/functions/sql_function.h",
+    ],
+)
+
 # GN target: //src/trace_processor/prelude/operators:operators
 perfetto_filegroup(
     name = "src_trace_processor_prelude_operators_operators",
@@ -1816,6 +2034,15 @@
     ],
 )
 
+# GN target: //src/trace_processor/prelude/table_functions:interface
+perfetto_filegroup(
+    name = "src_trace_processor_prelude_table_functions_interface",
+    srcs = [
+        "src/trace_processor/prelude/table_functions/table_function.cc",
+        "src/trace_processor/prelude/table_functions/table_function.h",
+    ],
+)
+
 # GN target: //src/trace_processor/prelude/table_functions:table_functions
 perfetto_filegroup(
     name = "src_trace_processor_prelude_table_functions_table_functions",
@@ -1840,13 +2067,46 @@
         "src/trace_processor/prelude/table_functions/experimental_slice_layout.h",
         "src/trace_processor/prelude/table_functions/flamegraph_construction_algorithms.cc",
         "src/trace_processor/prelude/table_functions/flamegraph_construction_algorithms.h",
-        "src/trace_processor/prelude/table_functions/table_function.cc",
-        "src/trace_processor/prelude/table_functions/table_function.h",
         "src/trace_processor/prelude/table_functions/view.cc",
         "src/trace_processor/prelude/table_functions/view.h",
     ],
 )
 
+# GN target: //src/trace_processor/prelude/table_functions:tables
+perfetto_cc_tp_tables(
+    name = "src_trace_processor_prelude_table_functions_tables",
+    srcs = [
+        "src/trace_processor/prelude/table_functions/tables.py",
+    ],
+    deps = [
+        ":src_trace_processor_tables_tables_python",
+    ],
+    outs = [
+        "src/trace_processor/prelude/table_functions/tables_py.h",
+    ],
+)
+
+# GN target: //src/trace_processor/prelude/tables_views:sources
+perfetto_filegroup(
+    name = "src_trace_processor_prelude_tables_views_sources",
+    srcs = [
+        "src/trace_processor/prelude/tables_views/tables.sql",
+        "src/trace_processor/prelude/tables_views/views.sql",
+    ],
+)
+
+# GN target: //src/trace_processor/prelude/tables_views:tables_views
+perfetto_cc_amalgamated_sql(
+    name = "src_trace_processor_prelude_tables_views_tables_views",
+    deps = [
+        ":src_trace_processor_prelude_tables_views_sources",
+    ],
+    outs = [
+        "src/trace_processor/prelude/tables_views/tables_views.h",
+    ],
+    namespace = "prelude::tables_views",
+)
+
 # GN target: //src/trace_processor/rpc:httpd
 perfetto_filegroup(
     name = "src_trace_processor_rpc_httpd",
@@ -1878,6 +2138,15 @@
     ],
 )
 
+# GN target: //src/trace_processor/sqlite:query_constraints
+perfetto_filegroup(
+    name = "src_trace_processor_sqlite_query_constraints",
+    srcs = [
+        "src/trace_processor/sqlite/query_constraints.cc",
+        "src/trace_processor/sqlite/query_constraints.h",
+    ],
+)
+
 # GN target: //src/trace_processor/sqlite:sqlite
 perfetto_filegroup(
     name = "src_trace_processor_sqlite_sqlite",
@@ -1885,10 +2154,13 @@
         "src/trace_processor/sqlite/db_sqlite_table.cc",
         "src/trace_processor/sqlite/db_sqlite_table.h",
         "src/trace_processor/sqlite/query_cache.h",
+        "src/trace_processor/sqlite/scoped_db.h",
         "src/trace_processor/sqlite/sql_stats_table.cc",
         "src/trace_processor/sqlite/sql_stats_table.h",
-        "src/trace_processor/sqlite/sqlite_raw_table.cc",
-        "src/trace_processor/sqlite/sqlite_raw_table.h",
+        "src/trace_processor/sqlite/sqlite_engine.cc",
+        "src/trace_processor/sqlite/sqlite_engine.h",
+        "src/trace_processor/sqlite/sqlite_table.cc",
+        "src/trace_processor/sqlite/sqlite_table.h",
         "src/trace_processor/sqlite/sqlite_utils.cc",
         "src/trace_processor/sqlite/sqlite_utils.h",
         "src/trace_processor/sqlite/stats_table.cc",
@@ -1896,19 +2168,6 @@
     ],
 )
 
-# GN target: //src/trace_processor/sqlite:sqlite_minimal
-perfetto_filegroup(
-    name = "src_trace_processor_sqlite_sqlite_minimal",
-    srcs = [
-        "src/trace_processor/sqlite/query_constraints.cc",
-        "src/trace_processor/sqlite/query_constraints.h",
-        "src/trace_processor/sqlite/scoped_db.h",
-        "src/trace_processor/sqlite/sqlite_table.cc",
-        "src/trace_processor/sqlite/sqlite_table.h",
-        "src/trace_processor/sqlite/sqlite_utils.h",
-    ],
-)
-
 # GN target: //src/trace_processor/stdlib/android/startup:startup
 perfetto_filegroup(
     name = "src_trace_processor_stdlib_android_startup_startup",
@@ -1925,10 +2184,13 @@
     name = "src_trace_processor_stdlib_android_android",
     srcs = [
         "src/trace_processor/stdlib/android/battery.sql",
+        "src/trace_processor/stdlib/android/battery_stats.sql",
         "src/trace_processor/stdlib/android/binder.sql",
         "src/trace_processor/stdlib/android/monitor_contention.sql",
+        "src/trace_processor/stdlib/android/network_packets.sql",
         "src/trace_processor/stdlib/android/process_metadata.sql",
         "src/trace_processor/stdlib/android/slices.sql",
+        "src/trace_processor/stdlib/android/statsd.sql",
     ],
 )
 
@@ -1936,6 +2198,7 @@
 perfetto_filegroup(
     name = "src_trace_processor_stdlib_chrome_chrome_sql",
     srcs = [
+        "src/trace_processor/stdlib/chrome/chrome_scrolls.sql",
         "src/trace_processor/stdlib/chrome/cpu_powerups.sql",
     ],
 )
@@ -1945,6 +2208,7 @@
     name = "src_trace_processor_stdlib_common_common",
     srcs = [
         "src/trace_processor/stdlib/common/counters.sql",
+        "src/trace_processor/stdlib/common/cpus.sql",
         "src/trace_processor/stdlib/common/metadata.sql",
         "src/trace_processor/stdlib/common/percentiles.sql",
         "src/trace_processor/stdlib/common/slices.sql",
@@ -2001,12 +2265,7 @@
 perfetto_filegroup(
     name = "src_trace_processor_tables_tables",
     srcs = [
-        "src/trace_processor/tables/counter_tables.h",
-        "src/trace_processor/tables/flow_tables.h",
-        "src/trace_processor/tables/macros.h",
         "src/trace_processor/tables/macros_internal.h",
-        "src/trace_processor/tables/profiler_tables.h",
-        "src/trace_processor/tables/slice_tables.h",
         "src/trace_processor/tables/table_destructors.cc",
     ],
 )
@@ -2051,7 +2310,6 @@
         "src/trace_processor/types/task_state.h",
         "src/trace_processor/types/tcp_state.h",
         "src/trace_processor/types/trace_processor_context.h",
-        "src/trace_processor/types/variadic.cc",
         "src/trace_processor/types/variadic.h",
         "src/trace_processor/types/version_number.h",
     ],
@@ -2591,6 +2849,15 @@
     ],
 )
 
+# GN target: //src/tracing/core:zlib_compressor
+perfetto_filegroup(
+    name = "src_tracing_core_zlib_compressor",
+    srcs = [
+        "src/tracing/core/zlib_compressor.cc",
+        "src/tracing/core/zlib_compressor.h",
+    ],
+)
+
 # GN target: //src/tracing/ipc/consumer:consumer
 perfetto_filegroup(
     name = "src_tracing_ipc_consumer_consumer",
@@ -3003,6 +3270,31 @@
     ],
 )
 
+# GN target: //protos/perfetto/cloud_trace_processor:lite
+perfetto_cc_proto_library(
+    name = "protos_perfetto_cloud_trace_processor_lite",
+    deps = [
+        ":protos_perfetto_cloud_trace_processor_protos",
+    ],
+)
+
+# GN target: //protos/perfetto/cloud_trace_processor:source_set
+perfetto_proto_library(
+    name = "protos_perfetto_cloud_trace_processor_protos",
+    srcs = [
+        "protos/perfetto/cloud_trace_processor/common.proto",
+        "protos/perfetto/cloud_trace_processor/orchestrator.proto",
+        "protos/perfetto/cloud_trace_processor/worker.proto",
+    ],
+    visibility = [
+        PERFETTO_CONFIG.proto_library_visibility,
+    ],
+    deps = [
+        ":protos_perfetto_common_protos",
+        ":protos_perfetto_trace_processor_protos",
+    ],
+)
+
 # GN target: //protos/perfetto/common:cpp
 perfetto_cc_protocpp_library(
     name = "protos_perfetto_common_cpp",
@@ -3011,6 +3303,14 @@
     ],
 )
 
+# GN target: //protos/perfetto/common:lite
+perfetto_cc_proto_library(
+    name = "protos_perfetto_common_lite",
+    deps = [
+        ":protos_perfetto_common_protos",
+    ],
+)
+
 # GN target: //protos/perfetto/common:source_set
 perfetto_proto_library(
     name = "protos_perfetto_common_protos",
@@ -4152,6 +4452,14 @@
     ],
 )
 
+# GN target: //protos/perfetto/trace_processor:lite
+perfetto_cc_proto_library(
+    name = "protos_perfetto_trace_processor_lite",
+    deps = [
+        ":protos_perfetto_trace_processor_protos",
+    ],
+)
+
 # GN target: //protos/perfetto/trace_processor:metrics_impl_source_set
 perfetto_proto_library(
     name = "protos_perfetto_trace_processor_metrics_impl_protos",
@@ -4175,7 +4483,6 @@
 perfetto_proto_library(
     name = "protos_perfetto_trace_processor_protos",
     srcs = [
-        "protos/perfetto/trace_processor/cloud_trace_processor.proto",
         "protos/perfetto/trace_processor/metatrace_categories.proto",
         "protos/perfetto/trace_processor/stack.proto",
         "protos/perfetto/trace_processor/trace_processor.proto",
@@ -4713,11 +5020,14 @@
         ":src_trace_processor_metatrace",
         ":src_trace_processor_metrics_metrics",
         ":src_trace_processor_prelude_functions_functions",
+        ":src_trace_processor_prelude_functions_interface",
         ":src_trace_processor_prelude_operators_operators",
+        ":src_trace_processor_prelude_table_functions_interface",
         ":src_trace_processor_prelude_table_functions_table_functions",
+        ":src_trace_processor_prelude_table_functions_tables",
         ":src_trace_processor_sorter_sorter",
+        ":src_trace_processor_sqlite_query_constraints",
         ":src_trace_processor_sqlite_sqlite",
-        ":src_trace_processor_sqlite_sqlite_minimal",
         ":src_trace_processor_storage_minimal",
         ":src_trace_processor_storage_storage",
         ":src_trace_processor_tables_tables",
@@ -4804,6 +5114,7 @@
                ":src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
                ":src_trace_processor_metrics_gen_cc_metrics_descriptor",
                ":src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+               ":src_trace_processor_prelude_tables_views_tables_views",
                ":src_trace_processor_stdlib_gen_amalgamated_stdlib",
            ] + PERFETTO_CONFIG.deps.jsoncpp +
            PERFETTO_CONFIG.deps.sqlite +
@@ -4865,13 +5176,16 @@
         ":src_trace_processor_metatrace",
         ":src_trace_processor_metrics_metrics",
         ":src_trace_processor_prelude_functions_functions",
+        ":src_trace_processor_prelude_functions_interface",
         ":src_trace_processor_prelude_operators_operators",
+        ":src_trace_processor_prelude_table_functions_interface",
         ":src_trace_processor_prelude_table_functions_table_functions",
+        ":src_trace_processor_prelude_table_functions_tables",
         ":src_trace_processor_rpc_httpd",
         ":src_trace_processor_rpc_rpc",
         ":src_trace_processor_sorter_sorter",
+        ":src_trace_processor_sqlite_query_constraints",
         ":src_trace_processor_sqlite_sqlite",
-        ":src_trace_processor_sqlite_sqlite_minimal",
         ":src_trace_processor_storage_minimal",
         ":src_trace_processor_storage_storage",
         ":src_trace_processor_tables_tables",
@@ -4948,6 +5262,7 @@
                ":src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
                ":src_trace_processor_metrics_gen_cc_metrics_descriptor",
                ":src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+               ":src_trace_processor_prelude_tables_views_tables_views",
                ":src_trace_processor_stdlib_gen_amalgamated_stdlib",
            ] + PERFETTO_CONFIG.deps.jsoncpp +
            PERFETTO_CONFIG.deps.linenoise +
@@ -5076,11 +5391,14 @@
         ":src_trace_processor_metatrace",
         ":src_trace_processor_metrics_metrics",
         ":src_trace_processor_prelude_functions_functions",
+        ":src_trace_processor_prelude_functions_interface",
         ":src_trace_processor_prelude_operators_operators",
+        ":src_trace_processor_prelude_table_functions_interface",
         ":src_trace_processor_prelude_table_functions_table_functions",
+        ":src_trace_processor_prelude_table_functions_tables",
         ":src_trace_processor_sorter_sorter",
+        ":src_trace_processor_sqlite_query_constraints",
         ":src_trace_processor_sqlite_sqlite",
-        ":src_trace_processor_sqlite_sqlite_minimal",
         ":src_trace_processor_storage_minimal",
         ":src_trace_processor_storage_storage",
         ":src_trace_processor_tables_tables",
@@ -5157,6 +5475,7 @@
                ":src_trace_processor_metrics_gen_cc_all_webview_metrics_descriptor",
                ":src_trace_processor_metrics_gen_cc_metrics_descriptor",
                ":src_trace_processor_metrics_sql_gen_amalgamated_sql_metrics",
+               ":src_trace_processor_prelude_tables_views_tables_views",
                ":src_trace_processor_stdlib_gen_amalgamated_stdlib",
                ":src_traceconv_gen_cc_trace_descriptor",
            ] + PERFETTO_CONFIG.deps.jsoncpp +
diff --git a/BUILD.gn b/BUILD.gn
index 77bafbc..c48159e 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -42,6 +42,7 @@
 }
 
 if (enable_perfetto_trace_processor && enable_perfetto_trace_processor_sqlite) {
+  all_targets += [ "src/cloud_trace_processor" ]
   all_targets += [ "src/trace_processor:trace_processor_shell" ]
 }
 
diff --git a/CHANGELOG b/CHANGELOG
index c81ed78..107a26b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,12 +1,28 @@
 Unreleased:
   Tracing service and probes:
-    * --continuous-dump in tools/java_heap_dump now keeps recording until it
-      receives CTRL+C.
+    * Compression has been moved from perfetto_cmd to traced. Now compression is
+      supported even with write_into_file. The `compress_from_cli` config option
+      can be used to restore the old behavior.
   Trace Processor:
     *
   UI:
     *
   SDK:
+    *
+
+v34.0 - 2023-05-02:
+  Tracing service and probes:
+    * --continuous-dump in tools/java_heap_dump now keeps recording until it
+      receives CTRL+C.
+    * Add CLONE_SNAPSHOT triggers for non-destructive snapshots of the trace
+      buffer without tracing interruption.
+  Trace Processor:
+    *
+  UI:
+    * Add support for parsing large integers from Trace Processor into
+      bigint. This is the default behaviour for unknown fields and can
+      be enabled specifically via the LONG and LONG_NULL column types.
+  SDK:
     * Changed the type of the static constexpr metadata on protozero
       generated bindings from a function returning the metadata to
       metadata itself. For a field 'foo' the variable kFoo previously
@@ -19,6 +35,9 @@
     * The new DataSourceBase::OnFlush() method allows users to properly handle
       Flush requests.
 
+v33.1 - 2023-03-03:
+  Identical to v33.0. Version was bumped to work around prebuilt infra failures.
+
 v33.0 - 2023-03-02:
   All:
     * Switched to a C++17-only project by removing C++11 opt-out. This completes
diff --git a/bazel/deps.bzl b/bazel/deps.bzl
index 27c5c80..a105a63 100644
--- a/bazel/deps.bzl
+++ b/bazel/deps.bzl
@@ -67,10 +67,10 @@
     )
 
     _add_repo_if_not_existing(
-        new_git_repository,
+        http_archive,
         name = "perfetto_dep_zlib",
-        remote = "https://android.googlesource.com/platform/external/zlib.git",
-        commit = "6d3f6aa0f87c9791ca7724c279ef61384f331dfd",
+        url = "https://storage.googleapis.com/perfetto/zlib-6d3f6aa0f87c9791ca7724c279ef61384f331dfd.tar.gz",
+        sha256 = "e9a1d6e8c936de68628ffb83a13d28a40cd6b2def2ad9378e8b951d4b8f4df18",
         build_file = "//bazel:zlib.BUILD",
     )
 
diff --git a/bazel/rules.bzl b/bazel/rules.bzl
index 4021c58..79174bd 100644
--- a/bazel/rules.bzl
+++ b/bazel/rules.bzl
@@ -308,7 +308,7 @@
         **kwargs,
     )
 
-def perfetto_cc_tp_tables(name, srcs, outs, **kwargs):
+def perfetto_cc_tp_tables(name, srcs, outs, deps = [], **kwargs):
     if PERFETTO_CONFIG.root[:2] != "//":
         fail("Expected PERFETTO_CONFIG.root to start with //")
 
@@ -317,12 +317,21 @@
     else:
         python_path = PERFETTO_CONFIG.root + "/python"
 
-    perfetto_py_binary(
-        name = name + "_tool",
+    perfetto_py_library(
+        name = name + "_lib",
         deps = [
             python_path + ":trace_processor_table_generator",
         ],
-        srcs = srcs + [
+        srcs = srcs,
+    )
+
+    perfetto_py_binary(
+        name = name + "_tool",
+        deps = [
+            ":" + name + "_lib",
+            python_path + ":trace_processor_table_generator",
+        ] + [d + "_lib" for d in deps],
+        srcs = [
             "tools/gen_tp_table_headers.py",
         ],
         main = "tools/gen_tp_table_headers.py",
@@ -332,9 +341,9 @@
     cmd = ["$(location " + name + "_tool)"]
     cmd += ["--gen-dir", "$(RULEDIR)"]
     cmd += ["--inputs", "$(SRCS)"]
-    cmd += ["--outputs", "$(OUTS)"]
     if PERFETTO_CONFIG.root != "//":
-        cmd += ["--header-prefix", PERFETTO_CONFIG.root[2:]]
+        cmd += ["--import-prefix", PERFETTO_CONFIG.root[2:]]
+        cmd += ["--relative-input-dir", PERFETTO_CONFIG.root[2:]]
 
     perfetto_genrule(
         name = name + "_gen",
diff --git a/buildtools/BUILD.gn b/buildtools/BUILD.gn
index 2ac2948..8eb19f7 100644
--- a/buildtools/BUILD.gn
+++ b/buildtools/BUILD.gn
@@ -129,6 +129,13 @@
 
   defines = [ "HAVE_PTHREAD=1" ]
   cflags = []
+
+  # Fixed upstream in:
+  # https://github.com/protocolbuffers/protobuf/pull/10112
+  # But we don't have that yet.
+  if (is_mac) {
+    cflags += [ "-Wno-deprecated-declarations" ]
+  }
   if (!is_clang && !is_win) {  # implies gcc
     cflags += [ "-Wno-stringop-overread" ]
   }
diff --git a/docs/analysis/trace-processor.md b/docs/analysis/trace-processor.md
index eb022e0..f1ef185 100644
--- a/docs/analysis/trace-processor.md
+++ b/docs/analysis/trace-processor.md
@@ -256,10 +256,10 @@
 up in the `args` table.
 
 For example, to retrieve the `prev_comm` field for `sched_switch` events in
-the `raw` table.
+the `ftrace_event` table.
 ```sql
 SELECT EXTRACT_ARG(arg_set_id, 'prev_comm')
-FROM raw
+FROM ftrace_event
 WHERE name = 'sched_switch'
 ```
 
@@ -271,7 +271,7 @@
     FROM args
     WHERE key = 'prev_comm' AND args.arg_set_id = raw.arg_set_id
   )
-FROM raw
+FROM ftrace_event
 WHERE name = 'sched_switch'
 ```
 
diff --git a/docs/contributing/perfetto-in-the-press.md b/docs/contributing/perfetto-in-the-press.md
index 108849a..4546bd6 100644
--- a/docs/contributing/perfetto-in-the-press.md
+++ b/docs/contributing/perfetto-in-the-press.md
@@ -2,6 +2,8 @@
 
 This a partial collection of the talks, blogposts, presentations, and articles that mention Perfetto.
 
+- [Google IO 2023 - What's new in Dart and Flutter](https://youtu.be/yRlwOdCK7Ho?t=798)
+- [Google IO 2023 - Debugging Jetpack Compose](https://youtu.be/Kp-aiSU8qCU?t=1092)
 - [Performance: Perfetto Traceviewer - MAD Skills](https://www.youtube.com/watch?v=phhLFicMacY)
 "On this episode of the MAD Skills series on Performance, Android Performance Engineer Carmen Jackson discusses the Perfetto traceviewer, an alternative to Android Studio for viewing system traces."
 - [Performance and optimisation on the Meta Quest Platform](https://m.facebook.com/RealityLabs/videos/performance-and-optimization-on-meta-quest-platform/488126049869673/)
diff --git a/docs/data-sources/cpu-scheduling.md b/docs/data-sources/cpu-scheduling.md
index 062d9b9..b8447ff 100644
--- a/docs/data-sources/cpu-scheduling.md
+++ b/docs/data-sources/cpu-scheduling.md
@@ -16,12 +16,7 @@
 
 ## UI
 
-When zoomed out, the UI shows a quantized view of CPU usage, which collapses the
-scheduling information:
-
-![](/docs/images/cpu-bar-graphs.png "Quantized view of CPU run queues")
-
-However, by zooming in, the individual scheduling events become visible:
+The UI represents individual scheduling events as slices:
 
 ![](/docs/images/cpu-zoomed.png "Detailed view of CPU run queues")
 
diff --git a/docs/images/enable-profile-flame-graph.png b/docs/images/enable-profile-flame-graph.png
deleted file mode 100644
index 642ce73..0000000
--- a/docs/images/enable-profile-flame-graph.png
+++ /dev/null
Binary files differ
diff --git a/docs/instrumentation/tracing-sdk.md b/docs/instrumentation/tracing-sdk.md
index d4c72b0..809833b 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 v32.1
+git clone https://android.googlesource.com/platform/external/perfetto -b v34.0
 ```
 
 The SDK consists of two files, `sdk/perfetto.h` and `sdk/perfetto.cc`. These are
diff --git a/docs/quickstart/callstack-sampling.md b/docs/quickstart/callstack-sampling.md
index e5da5b2..cb47edc 100644
--- a/docs/quickstart/callstack-sampling.md
+++ b/docs/quickstart/callstack-sampling.md
@@ -119,11 +119,6 @@
 
 ## View profile
 
-Visualizing callstacks in the Perfetto UI is currently disabled behind a
-flag. Please enable it before proceeding further:
-
-![Enable flame graph flag](/docs/images/enable-profile-flame-graph.png)
-
 Upload the `raw-trace` or `symbolized-trace` file from the output directory to
 the [Perfetto UI](https://ui.perfetto.dev) and click and drag over one or more
 of the diamond markers in the UI track named "Perf Samples" for the processes
diff --git a/docs/quickstart/chrome-tracing.md b/docs/quickstart/chrome-tracing.md
index 4e17172..6786b4d 100644
--- a/docs/quickstart/chrome-tracing.md
+++ b/docs/quickstart/chrome-tracing.md
@@ -4,6 +4,8 @@
 
 > To record traces from Chrome on Android, follow the [instructions for recording Android system traces](/docs/quickstart/android-tracing.md) and enable the Chrome probe.
 
+>> If you are using [user build of Android](https://source.android.com/docs/setup/build/building#lunch), you'll have to enable integration with system Perfetto by switching chrome://flags#enable-perfetto-system-tracing to "Enabled" and restarting Chrome.
+
 ## Recording a trace
 
 1. Navigate to [ui.perfetto.dev](https://ui.perfetto.dev/) and select **"Record new trace"** from the left menu.
diff --git a/examples/sdk/README.md b/examples/sdk/README.md
index 238d50d..476ebbe 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 v32.1
+git clone https://android.googlesource.com/platform/external/perfetto -b v34.0
 ```
 
 Then, build using CMake:
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index 192ede8..749c2ee 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -275,7 +275,7 @@
     "GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
   ]
   cflags = []
-  if (!is_clang || !is_win) {
+  if (!is_clang && !is_win) {
     cflags += [ "-Wno-deprecated-declarations" ]
   }
   if (is_clang && is_win) {
@@ -289,6 +289,10 @@
       "-Wno-unused-parameter",
       "-Wno-shadow-field-in-constructor",
       "-Wno-zero-as-null-pointer-constant",
+
+      # Fixed in upstream protobuf v3.22.0
+      # d37cbfd4485f("Update inlined_string_field.h"), but we don't have that.
+      "-Wno-undef",
     ]
   }
 
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index b778d88..579fc0f 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -296,8 +296,9 @@
       enable_perfetto_trace_processor &&
       (perfetto_build_standalone || perfetto_build_with_android)
 
-  # Enables Zlib support. This is used both by the "perfetto" cmdline client
-  # (for compressing traces) and by trace processor (for compressed traces).
+  # Enables Zlib support. This is used to compress traces (by the tracing
+  # service and by the "perfetto" cmdline client) and to decompress traces (by
+  # trace_processor).
   enable_perfetto_zlib =
       (enable_perfetto_trace_processor && !build_with_chromium) ||
       enable_perfetto_platform_services
diff --git a/gn/perfetto_tp_tables.gni b/gn/perfetto_tp_tables.gni
index da8cfae..19a98c0 100644
--- a/gn/perfetto_tp_tables.gni
+++ b/gn/perfetto_tp_tables.gni
@@ -17,6 +17,10 @@
 template("perfetto_tp_tables") {
   config_name = target_name + "_config"
   action_name = target_name
+  relative_args = [
+    "--relative-input-dir",
+    rebase_path(perfetto_root_path, root_build_dir),
+  ]
 
   config(config_name) {
     include_dirs = [ root_gen_dir ]
@@ -32,15 +36,17 @@
     }
 
     deps = [ "$perfetto_root_path/python:trace_processor_table_generator" ]
+    if (defined(invoker.deps)) {
+      deps += invoker.deps
+    }
     public_configs = [ ":$config_name" ]
 
     gen_args = [
       "--gen-dir",
-      rebase_path("$root_gen_dir", root_build_dir),
+      rebase_path("$root_gen_dir/$perfetto_root_path", root_build_dir),
     ]
     input_args = [ "--inputs" ] + rebase_path(invoker.sources, root_build_dir)
-    output_args = [ "--outputs" ] + rebase_path(outputs, root_build_dir)
-    args = gen_args + input_args + output_args
+    args = gen_args + input_args + relative_args
 
     metadata = {
       perfetto_action_type_for_generator = [ "tp_tables" ]
@@ -56,7 +62,7 @@
       deps = [ "$perfetto_root_path/python:trace_processor_table_generator" ]
       outputs = [ "$target_gen_dir/$docs_name.json" ]
       args = [ "--out" ] + rebase_path(outputs, root_build_dir) +
-             rebase_path(invoker.sources, root_build_dir)
+             rebase_path(invoker.sources, root_build_dir) + relative_args
     }
   }
 }
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index 76594fe..b7b1e01 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -72,12 +72,10 @@
 
 if (enable_perfetto_trace_processor) {
   perfetto_unittests_targets += [ "src/trace_processor:unittests" ]
-
-  # TODO(mohitms): reenable this once we no longer link lite and full protobuf
-  # simultaneously.
-  # perfetto_unittests_targets += [ "src/traceconv:unittests" ]
+  perfetto_unittests_targets += [ "src/traceconv:unittests" ]
 
   if (enable_perfetto_trace_processor_sqlite) {
     perfetto_unittests_targets += [ "src/trace_processor/metrics:unittests" ]
+    perfetto_unittests_targets += [ "src/cloud_trace_processor:unittests" ]
   }
 }
diff --git a/gn/standalone/BUILD.gn b/gn/standalone/BUILD.gn
index d000f53..0af6551 100644
--- a/gn/standalone/BUILD.gn
+++ b/gn/standalone/BUILD.gn
@@ -100,6 +100,18 @@
     # TODO(lalitm): remove this when we upgrade to a GCC version which is good
     # enough to handle this.
     cflags_cc += [ "-Wno-maybe-uninitialized" ]
+
+    # GCC's handling of detecting infinite recursion is flaky at best and
+    # causes some false positives.
+    # TODO(lalitm): remove this when we upgrade to a GCC version which is good
+    # enough to handle this.
+    cflags_cc += [ "-Wno-infinite-recursion" ]
+
+    # GCC's handling of detecting non null arguments is flaky at best and
+    # causes some false positives.
+    # TODO(lalitm): remove this when we upgrade to a GCC version which is good
+    # enough to handle this.
+    cflags_cc += [ "-Wno-nonnull" ]
   }
 }
 
diff --git a/gn/standalone/toolchain/win_find_msvc.py b/gn/standalone/toolchain/win_find_msvc.py
index 1628c07..6f7eb86 100644
--- a/gn/standalone/toolchain/win_find_msvc.py
+++ b/gn/standalone/toolchain/win_find_msvc.py
@@ -27,6 +27,7 @@
 """
 
 import os
+import itertools
 import subprocess
 import sys
 
@@ -62,19 +63,19 @@
     filt = lambda x: os.path.exists(os.path.join(x, 'ucrt', 'x64', 'ucrt.lib'))
     out[1] = find_max_subdir(lib_base, filt)
 
-  for year in ['2022', '2021', '2020', '2019']:
-    for version in [
-        'BuildTools', 'Community', 'Professional', 'Enterprise', 'Preview'
-    ]:
-      msvc_base = ('C:\\Program Files (x86)\\Microsoft Visual Studio\\'
-                   f'{year}\\{version}\\VC\\Tools\\MSVC')
-      if os.path.exists(msvc_base):
-        filt = lambda x: os.path.exists(
-            os.path.join(x, 'lib', 'x64', 'libcmt.lib'))
-        max_msvc = find_max_subdir(msvc_base, filt)
-        if max_msvc is not None:
-          out[2] = os.path.join(msvc_base, max_msvc)
-        break
+  for try_dir in itertools.product(
+      ['2022', '2021', '2020', '2019'],
+      ['BuildTools', 'Community', 'Professional', 'Enterprise', 'Preview'],
+      ['Program Files', 'Program Files (x86)']):
+    msvc_base = (f'C:\\{try_dir[2]}\\Microsoft Visual Studio\\'
+                f'{try_dir[0]}\\{try_dir[1]}\\VC\\Tools\\MSVC')
+    if os.path.exists(msvc_base):
+      filt = lambda x: os.path.exists(
+          os.path.join(x, 'lib', 'x64', 'libcmt.lib'))
+      max_msvc = find_max_subdir(msvc_base, filt)
+      if max_msvc is not None:
+        out[2] = os.path.join(msvc_base, max_msvc)
+      break
 
   # Don't error in case of failure, GN scripts are supposed to deal with
   # failures and allow the user to override the dirs.
diff --git a/include/perfetto/base/compiler.h b/include/perfetto/base/compiler.h
index 451e6d5..22324ad 100644
--- a/include/perfetto/base/compiler.h
+++ b/include/perfetto/base/compiler.h
@@ -23,16 +23,6 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/public/compiler.h"
 
-#if __cplusplus >= 201703
-#define PERFETTO_IS_AT_LEAST_CPP17() 1
-#elif defined(_MSVC_LANG) && _MSVC_LANG >= 201703L
-// Without additional flags, MSVC is not standard compliant and keeps
-// __cplusplus stuck at an old value, even with C++17
-#define PERFETTO_IS_AT_LEAST_CPP17() 1
-#else
-#define PERFETTO_IS_AT_LEAST_CPP17() 0
-#endif
-
 // __has_attribute is supported only by clang and recent versions of GCC.
 // Add a layer to wrap the __has_attribute macro.
 #if defined(__has_attribute)
diff --git a/include/perfetto/ext/base/status_or.h b/include/perfetto/ext/base/status_or.h
index 46387d7..a6eda7e 100644
--- a/include/perfetto/ext/base/status_or.h
+++ b/include/perfetto/ext/base/status_or.h
@@ -73,6 +73,10 @@
   std::optional<T> value_;
 };
 
+// Deduction guide to make returning StatusOr less verbose.
+template <typename T>
+StatusOr(T) -> StatusOr<T>;
+
 }  // namespace base
 }  // namespace perfetto
 
diff --git a/include/perfetto/ext/base/threading/util.h b/include/perfetto/ext/base/threading/util.h
index 7b2465e..a08a4b0 100644
--- a/include/perfetto/ext/base/threading/util.h
+++ b/include/perfetto/ext/base/threading/util.h
@@ -136,9 +136,12 @@
 
    private:
     void RunFn() {
-      pool_->PostTask([channel = channel_, fn = fn_]() {
+      pool_->PostTask([channel = channel_, fn = fn_]() mutable {
         auto opt_value = (*fn)();
         if (!opt_value) {
+          // Clear out the function to ensure that any captured state is freed
+          // before we inform the caller.
+          fn.reset();
           channel->Close();
           return;
         }
diff --git a/include/perfetto/ext/cloud_trace_processor/BUILD.gn b/include/perfetto/ext/cloud_trace_processor/BUILD.gn
new file mode 100644
index 0000000..be266cb
--- /dev/null
+++ b/include/perfetto/ext/cloud_trace_processor/BUILD.gn
@@ -0,0 +1,26 @@
+# 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.
+
+source_set("cloud_trace_processor") {
+  sources = [
+    "environment.h",
+    "orchestrator.h",
+    "worker.h",
+  ]
+  deps = [
+    "../../../../gn:default_deps",
+    "../base",
+    "../base/threading",
+  ]
+}
diff --git a/include/perfetto/ext/cloud_trace_processor/environment.h b/include/perfetto/ext/cloud_trace_processor/environment.h
new file mode 100644
index 0000000..8b1fc01
--- /dev/null
+++ b/include/perfetto/ext/cloud_trace_processor/environment.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_CLOUD_TRACE_PROCESSOR_ENVIRONMENT_H_
+#define INCLUDE_PERFETTO_EXT_CLOUD_TRACE_PROCESSOR_ENVIRONMENT_H_
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/threading/stream.h"
+
+namespace perfetto {
+namespace cloud_trace_processor {
+
+// Shim interface allowing embedders to change how operations which interact
+// with the OS operate (e.g. IO, networking etc).
+class CtpEnvironment {
+ public:
+  virtual ~CtpEnvironment();
+
+  // Opens the file at |path| and reads the contents in chunks, returning the
+  // the chunks as a Stream. The size of the chunks is implementation defined
+  // but should be sized to balance memory use and syscall count.
+  virtual base::StatusOrStream<std::vector<uint8_t>> ReadFile(
+      const std::string& path) = 0;
+};
+
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_CLOUD_TRACE_PROCESSOR_ENVIRONMENT_H_
diff --git a/include/perfetto/ext/cloud_trace_processor/orchestrator.h b/include/perfetto/ext/cloud_trace_processor/orchestrator.h
new file mode 100644
index 0000000..7983161
--- /dev/null
+++ b/include/perfetto/ext/cloud_trace_processor/orchestrator.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_CLOUD_TRACE_PROCESSOR_ORCHESTRATOR_H_
+#define INCLUDE_PERFETTO_EXT_CLOUD_TRACE_PROCESSOR_ORCHESTRATOR_H_
+
+#include <memory>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/threading/future.h"
+#include "perfetto/ext/base/threading/stream.h"
+
+namespace perfetto {
+namespace protos {
+class TracePoolCreateArgs;
+class TracePoolCreateResponse;
+
+class TracePoolSetTracesArgs;
+class TracePoolSetTracesResponse;
+
+class TracePoolQueryArgs;
+class TracePoolQueryResponse;
+
+class TracePoolDestroyArgs;
+class TracePoolDestroyResponse;
+}  // namespace protos
+}  // namespace perfetto
+
+namespace perfetto {
+namespace cloud_trace_processor {
+
+class Worker;
+
+// Interface for a CloudTraceProcessor "Orchestrator".
+//
+// See CloudTraceProcessorOrchestrator RPC service for high-level documentation.
+class Orchestrator {
+ public:
+  virtual ~Orchestrator();
+
+  // Returns an in-process implementation of the Orchestrator, given a group of
+  // workers which can be delegated to.
+  //
+  // Note that the passed workers instances can be "remote" (i.e. in another
+  // process or even on another machine); the returned manager will gracefully
+  // handle this.
+  static std::unique_ptr<Orchestrator> CreateInProcess(
+      std::vector<std::unique_ptr<Worker>> workers);
+
+  // Creates a TracePool with the specified arguments.
+  virtual base::StatusOrFuture<protos::TracePoolCreateResponse> TracePoolCreate(
+      const protos::TracePoolCreateArgs&) = 0;
+
+  // Associates the provided list of traces to this TracePoolShard.
+  virtual base::StatusOrFuture<protos::TracePoolSetTracesResponse>
+  TracePoolSetTraces(const protos::TracePoolSetTracesArgs&) = 0;
+
+  // Executes a SQL query on the specified TracePool.
+  virtual base::StatusOrStream<protos::TracePoolQueryResponse> TracePoolQuery(
+      const protos::TracePoolQueryArgs&) = 0;
+
+  // Destroys the TracePool with the specified id.
+  virtual base::StatusOrFuture<protos::TracePoolDestroyResponse>
+  TracePoolDestroy(const protos::TracePoolDestroyArgs&) = 0;
+};
+
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_CLOUD_TRACE_PROCESSOR_ORCHESTRATOR_H_
diff --git a/include/perfetto/ext/cloud_trace_processor/worker.h b/include/perfetto/ext/cloud_trace_processor/worker.h
new file mode 100644
index 0000000..60dbe77
--- /dev/null
+++ b/include/perfetto/ext/cloud_trace_processor/worker.h
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_CLOUD_TRACE_PROCESSOR_WORKER_H_
+#define INCLUDE_PERFETTO_EXT_CLOUD_TRACE_PROCESSOR_WORKER_H_
+
+#include <memory>
+#include <vector>
+
+#include "perfetto/ext/base/threading/future.h"
+#include "perfetto/ext/base/threading/stream.h"
+
+namespace perfetto {
+
+namespace base {
+class ThreadPool;
+}
+
+namespace protos {
+class TracePoolShardCreateArgs;
+class TracePoolShardCreateResponse;
+
+class TracePoolShardSetTracesArgs;
+class TracePoolShardSetTracesResponse;
+
+class TracePoolShardQueryArgs;
+class TracePoolShardQueryResponse;
+
+class TracePoolShardDestroyArgs;
+class TracePoolShardDestroyResponse;
+}  // namespace protos
+
+namespace cloud_trace_processor {
+
+class CtpEnvironment;
+
+// Interface for a CloudTraceProcessor "Worker".
+//
+// See CloudTraceProcessorWorker RPC service for high-level documentation.
+class Worker {
+ public:
+  virtual ~Worker();
+
+  // Returns an in-process implementation of the Worker given an instance of
+  // |CtpEnvironment| and a |ThreadPool|. The |CtpEnvironment| will be used to
+  // perform any interaction with the OS (e.g. opening and reading files) and
+  // the |ThreadPool| will be used to dispatch requests to TraceProcessor.
+  static std::unique_ptr<Worker> CreateInProcesss(CtpEnvironment*,
+                                                  base::ThreadPool*);
+
+  // Creates a TracePoolShard which will be owned by this worker.
+  virtual base::StatusOrFuture<protos::TracePoolShardCreateResponse>
+  TracePoolShardCreate(const protos::TracePoolShardCreateArgs&) = 0;
+
+  // Associates the provided list of traces to this TracePoolShard.
+  virtual base::StatusOrStream<protos::TracePoolShardSetTracesResponse>
+  TracePoolShardSetTraces(const protos::TracePoolShardSetTracesArgs&) = 0;
+
+  // Executes a SQL query on the specified TracePoolShard.
+  virtual base::StatusOrStream<protos::TracePoolShardQueryResponse>
+  TracePoolShardQuery(const protos::TracePoolShardQueryArgs&) = 0;
+
+  // Destroys the TracePoolShard with the specified id.
+  virtual base::StatusOrFuture<protos::TracePoolShardDestroyResponse>
+  TracePoolShardDestroy(const protos::TracePoolShardDestroyArgs&) = 0;
+};
+
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_CLOUD_TRACE_PROCESSOR_WORKER_H_
diff --git a/include/perfetto/ext/tracing/core/consumer.h b/include/perfetto/ext/tracing/core/consumer.h
index 7a7d81a..f55b810 100644
--- a/include/perfetto/ext/tracing/core/consumer.h
+++ b/include/perfetto/ext/tracing/core/consumer.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "perfetto/base/export.h"
+#include "perfetto/ext/base/uuid.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/observable_events.h"
 #include "perfetto/tracing/core/forward_decls.h"
@@ -81,7 +82,12 @@
   // Called back by the Service (or transport layer) after invoking
   // TracingService::ConsumerEndpoint::CloneSession().
   // TODO(primiano): make pure virtual after various 3way patches.
-  virtual void OnSessionCloned(bool success, const std::string& error);
+  struct OnSessionClonedArgs {
+    bool success;
+    std::string error;
+    base::Uuid uuid;  // UUID of the cloned session.
+  };
+  virtual void OnSessionCloned(const OnSessionClonedArgs&);
 };
 
 }  // namespace perfetto
diff --git a/include/perfetto/ext/tracing/core/tracing_service.h b/include/perfetto/ext/tracing/core/tracing_service.h
index 82e53f7..b82a96a 100644
--- a/include/perfetto/ext/tracing/core/tracing_service.h
+++ b/include/perfetto/ext/tracing/core/tracing_service.h
@@ -28,6 +28,7 @@
 #include "perfetto/ext/base/sys_types.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
 #include "perfetto/ext/tracing/core/shared_memory.h"
+#include "perfetto/ext/tracing/core/trace_packet.h"
 #include "perfetto/tracing/buffer_exhausted_policy.h"
 #include "perfetto/tracing/core/forward_decls.h"
 
@@ -253,6 +254,14 @@
   virtual void SaveTraceForBugreport(SaveTraceForBugreportCallback) = 0;
 };  // class ConsumerEndpoint.
 
+struct PERFETTO_EXPORT_COMPONENT TracingServiceInitOpts {
+  // Function used by tracing service to compress packets. Takes a pointer to
+  // a vector of TracePackets and replaces the packets in the vector with
+  // compressed ones.
+  using CompressorFn = void (*)(std::vector<TracePacket>*);
+  CompressorFn compressor_fn = nullptr;
+};
+
 // The public API of the tracing Service business logic.
 //
 // Exposed to:
@@ -267,6 +276,7 @@
  public:
   using ProducerEndpoint = perfetto::ProducerEndpoint;
   using ConsumerEndpoint = perfetto::ConsumerEndpoint;
+  using InitOpts = TracingServiceInitOpts;
 
   // Default sizes used by the service implementation and client library.
   static constexpr size_t kDefaultShmPageSize = 4096ul;
@@ -286,10 +296,12 @@
     kDisabled
   };
 
-  // Implemented in src/core/tracing_service_impl.cc .
+  // Implemented in src/core/tracing_service_impl.cc . CompressorFn can be
+  // nullptr, in which case TracingService will not support compression.
   static std::unique_ptr<TracingService> CreateInstance(
       std::unique_ptr<SharedMemory::Factory>,
-      base::TaskRunner*);
+      base::TaskRunner*,
+      InitOpts init_opts = {});
 
   virtual ~TracingService();
 
diff --git a/include/perfetto/ext/tracing/ipc/service_ipc_host.h b/include/perfetto/ext/tracing/ipc/service_ipc_host.h
index 5c51c4a..b24ccb5 100644
--- a/include/perfetto/ext/tracing/ipc/service_ipc_host.h
+++ b/include/perfetto/ext/tracing/ipc/service_ipc_host.h
@@ -23,6 +23,7 @@
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
+#include "perfetto/ext/tracing/core/tracing_service.h"
 
 namespace perfetto {
 namespace base {
@@ -33,8 +34,6 @@
 class Host;
 }  // namespace ipc
 
-class TracingService;
-
 // Creates an instance of the service (business logic + UNIX socket transport).
 // Exposed to:
 //   The code in the tracing client that will host the service e.g., traced.
@@ -42,7 +41,9 @@
 //   src/tracing/ipc/service/service_ipc_host_impl.cc
 class PERFETTO_EXPORT_COMPONENT ServiceIPCHost {
  public:
-  static std::unique_ptr<ServiceIPCHost> CreateInstance(base::TaskRunner*);
+  static std::unique_ptr<ServiceIPCHost> CreateInstance(
+      base::TaskRunner*,
+      TracingService::InitOpts = {});
   virtual ~ServiceIPCHost();
 
   // Start listening on the Producer & Consumer ports. Returns false in case of
diff --git a/include/perfetto/protozero/proto_decoder.h b/include/perfetto/protozero/proto_decoder.h
index 2210532..c27fcad 100644
--- a/include/perfetto/protozero/proto_decoder.h
+++ b/include/perfetto/protozero/proto_decoder.h
@@ -340,7 +340,8 @@
       uint32_t field_id,
       bool* parse_error_location) const {
     const Field& field = Get(field_id);
-    if (field.valid()) {
+    if (field.valid() &&
+        field.type() == proto_utils::ProtoWireType::kLengthDelimited) {
       return PackedRepeatedFieldIterator<wire_type, cpp_type>(
           field.data(), field.size(), parse_error_location);
     }
diff --git a/include/perfetto/tracing/core/trace_config.h b/include/perfetto/tracing/core/trace_config.h
index 3e2a830..e9807ab 100644
--- a/include/perfetto/tracing/core/trace_config.h
+++ b/include/perfetto/tracing/core/trace_config.h
@@ -25,4 +25,16 @@
 
 #include "protos/perfetto/config/trace_config.gen.h"
 
+namespace perfetto {
+
+inline TraceConfig::TriggerConfig::TriggerMode GetTriggerMode(
+    const TraceConfig& cfg) {
+  auto mode = cfg.trigger_config().trigger_mode();
+  if (cfg.trigger_config().use_clone_snapshot_if_available())
+    mode = TraceConfig::TriggerConfig::CLONE_SNAPSHOT;
+  return mode;
+}
+
+}  // namespace perfetto
+
 #endif  // INCLUDE_PERFETTO_TRACING_CORE_TRACE_CONFIG_H_
diff --git a/include/perfetto/tracing/data_source.h b/include/perfetto/tracing/data_source.h
index 95036d1..26525fa 100644
--- a/include/perfetto/tracing/data_source.h
+++ b/include/perfetto/tracing/data_source.h
@@ -105,7 +105,7 @@
   };
   virtual void OnStart(const StartArgs&);
 
-  class StopArgs {
+  class PERFETTO_EXPORT_COMPONENT StopArgs {
    public:
     virtual ~StopArgs();
 
@@ -190,6 +190,22 @@
   }
 };
 
+// Holds the type for a DataSource. Accessed by the static Trace() method
+// fastpaths. This allows redefinitions under a component where a component
+// specific export macro is used.
+// Due to C2086 (redefinition) error on MSVC/clang-cl, internal::DataSourceType
+// can't be a static data member. To avoid explicit specialization after
+// instantiation error, type() needs to be in a template helper class that's
+// instantiated independently from DataSource. See b/280777748.
+template <typename DerivedDataSource,
+          typename DataSourceTraits = DefaultDataSourceTraits>
+struct DataSourceHelper {
+  static internal::DataSourceType& type() {
+    static perfetto::internal::DataSourceType type_;
+    return type_;
+  }
+};
+
 // Templated base class meant to be derived by embedders to create a custom data
 // source. DerivedDataSource must be the type of the derived class itself, e.g.:
 // class MyDataSource : public DataSource<MyDataSource> {...}.
@@ -200,6 +216,7 @@
           typename DataSourceTraits = DefaultDataSourceTraits>
 class DataSource : public DataSourceBase {
   struct DefaultTracePointTraits;
+  using Helper = DataSourceHelper<DerivedDataSource, DataSourceTraits>;
 
  public:
   // The BufferExhaustedPolicy to use for TraceWriters of this DataSource.
@@ -286,7 +303,8 @@
     // validity before using it. After checking, the handle is guaranteed to
     // remain valid until the handle goes out of scope.
     LockedHandle<DerivedDataSource> GetDataSourceLocked() const {
-      auto* internal_state = type_.static_state()->TryGet(instance_index_);
+      auto* internal_state =
+          Helper::type().static_state()->TryGet(instance_index_);
       if (!internal_state)
         return LockedHandle<DerivedDataSource>();
       std::unique_lock<std::recursive_mutex> lock(internal_state->lock);
@@ -304,7 +322,7 @@
 
     typename DataSourceTraits::IncrementalStateType* GetIncrementalState() {
       return static_cast<typename DataSourceTraits::IncrementalStateType*>(
-          type_.GetIncrementalState(tls_inst_, instance_index_));
+          Helper::type().GetIncrementalState(tls_inst_, instance_index_));
     }
 
    private:
@@ -382,20 +400,20 @@
       typename Traits::TracePointData trace_point_data = {}) {
     PERFETTO_DCHECK(cached_instances);
 
-    if (!type_.TracePrologue<DataSourceTraits, Traits>(
+    if (!Helper::type().template TracePrologue<DataSourceTraits, Traits>(
             &tls_state_, &cached_instances, trace_point_data)) {
       return;
     }
 
     for (internal::DataSourceType::InstancesIterator it =
-             type_.BeginIteration<Traits>(cached_instances, tls_state_,
-                                          trace_point_data);
-         it.instance;
-         type_.NextIteration<Traits>(&it, tls_state_, trace_point_data)) {
+             Helper::type().template BeginIteration<Traits>(
+                 cached_instances, tls_state_, trace_point_data);
+         it.instance; Helper::type().template NextIteration<Traits>(
+             &it, tls_state_, trace_point_data)) {
       tracing_fn(TraceContext(it.instance, it.i));
     }
 
-    type_.TraceEpilogue(tls_state_);
+    Helper::type().TraceEpilogue(tls_state_);
   }
 
   // Registers the data source on all tracing backends, including ones that
@@ -413,7 +431,6 @@
                        const Args&... constructor_args) {
     // Silences -Wunused-variable warning in case the trace method is not used
     // by the translation unit that declares the data source.
-    (void)type_;
     (void)tls_state_;
 
     auto factory = [constructor_args...]() {
@@ -423,7 +440,7 @@
     internal::DataSourceParams params{
         DerivedDataSource::kSupportsMultipleInstances,
         DerivedDataSource::kRequiresCallbacksUnderLock};
-    return type_.Register(
+    return Helper::type().Register(
         descriptor, factory, params, DerivedDataSource::kBufferExhaustedPolicy,
         GetCreateTlsFn(
             static_cast<typename DataSourceTraits::TlsStateType*>(nullptr)),
@@ -435,7 +452,7 @@
 
   // Updates the data source descriptor.
   static void UpdateDescriptor(const DataSourceDescriptor& descriptor) {
-    type_.UpdateDescriptor(descriptor);
+    Helper::type().UpdateDescriptor(descriptor);
   }
 
  private:
@@ -456,7 +473,7 @@
     // implement per-category enabled states.
     struct TracePointData {};
     static constexpr std::atomic<uint32_t>* GetActiveInstances(TracePointData) {
-      return type_.valid_instances();
+      return Helper::type().valid_instances();
     }
   };
 
@@ -506,10 +523,6 @@
     return nullptr;
   }
 
-  // The type of this data source. Accessed by the static Trace() method
-  // fastpaths.
-  static internal::DataSourceType type_;
-
   // This TLS object is a cached raw pointer and has deliberately no destructor.
   // The Platform implementation is supposed to create and manage the lifetime
   // of the Platform::ThreadLocalObject and take care of destroying it.
@@ -522,9 +535,6 @@
 
 // static
 template <typename T, typename D>
-internal::DataSourceType DataSource<T, D>::type_;
-// static
-template <typename T, typename D>
 PERFETTO_THREAD_LOCAL internal::DataSourceThreadLocalState*
     DataSource<T, D>::tls_state_;
 
@@ -547,16 +557,11 @@
 // where a component specific export macro is used.
 #define PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS(attrs, ...) \
   template <>                                                              \
-  attrs perfetto::internal::DataSourceType                                 \
-      perfetto::DataSource<__VA_ARGS__>::type_
+  attrs perfetto::internal::DataSourceType&                                \
+  perfetto::DataSourceHelper<__VA_ARGS__>::type()
 
 // This macro must be used once for each data source in one source file to
 // allocate static storage for the data source's static state.
-//
-// Note: if MSVC fails with a C2086 (redefinition) error here, use the
-// permissive- flag to enable standards-compliant mode. See
-// https://developercommunity.visualstudio.com/content/problem/319447/
-// explicit-specialization-of-static-data-member-inco.html.
 #define PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(...)  \
   PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS( \
       PERFETTO_COMPONENT_EXPORT, __VA_ARGS__)
@@ -566,7 +571,11 @@
 // where a component specific export macro is used.
 #define PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS(attrs, ...) \
   template <>                                                             \
-  attrs perfetto::internal::DataSourceType                                \
-      perfetto::DataSource<__VA_ARGS__>::type_ {}
+  perfetto::internal::DataSourceType&                                     \
+  perfetto::DataSourceHelper<__VA_ARGS__>::type() {                       \
+    static perfetto::internal::DataSourceType type_;                      \
+    return type_;                                                         \
+  }                                                                       \
+  PERFETTO_INTERNAL_SWALLOW_SEMICOLON()
 
 #endif  // INCLUDE_PERFETTO_TRACING_DATA_SOURCE_H_
diff --git a/include/perfetto/tracing/interceptor.h b/include/perfetto/tracing/interceptor.h
index b800cb3..637c8bf 100644
--- a/include/perfetto/tracing/interceptor.h
+++ b/include/perfetto/tracing/interceptor.h
@@ -187,7 +187,7 @@
   // To define your own state, subclass this with the same name in the
   // interceptor class. A reference to the state can then be looked up through
   // context.GetThreadLocalState() in the trace packet interceptor function.
-  class ThreadLocalState {
+  class PERFETTO_EXPORT_COMPONENT ThreadLocalState {
    public:
     virtual ~ThreadLocalState();
   };
diff --git a/include/perfetto/tracing/internal/track_event_macros.h b/include/perfetto/tracing/internal/track_event_macros.h
index 6d2777e..f10ef39 100644
--- a/include/perfetto/tracing/internal/track_event_macros.h
+++ b/include/perfetto/tracing/internal/track_event_macros.h
@@ -156,7 +156,7 @@
 // C++17 doesn't like a move constructor being defined for the EventFinalizer
 // class but C++11 and MSVC doesn't compile without it being defined so support
 // both.
-#if PERFETTO_IS_AT_LEAST_CPP17() && !PERFETTO_BUILDFLAG(PERFETTO_COMPILER_MSVC)
+#if !PERFETTO_BUILDFLAG(PERFETTO_COMPILER_MSVC)
 #define PERFETTO_INTERNAL_EVENT_FINALIZER_KEYWORD delete
 #else
 #define PERFETTO_INTERNAL_EVENT_FINALIZER_KEYWORD default
diff --git a/protos/perfetto/cloud_trace_processor/BUILD.gn b/protos/perfetto/cloud_trace_processor/BUILD.gn
new file mode 100644
index 0000000..8346434
--- /dev/null
+++ b/protos/perfetto/cloud_trace_processor/BUILD.gn
@@ -0,0 +1,38 @@
+# Copyright (C) 2018 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/proto_library.gni")
+
+SOURCES = [
+  "common.proto",
+  "orchestrator.proto",
+  "worker.proto",
+]
+
+perfetto_proto_library("@TYPE@") {
+  proto_generators = [
+    "lite",
+    "zero",
+    "source_set",
+  ]
+  deps = [ "../trace_processor:@TYPE@" ]  # needed for descriptor.proto.
+  sources = SOURCES
+}
+
+if (enable_perfetto_grpc) {
+  perfetto_grpc_library("cloud_trace_processor_grpc") {
+    deps = [ ":lite" ]
+    sources = SOURCES
+  }
+}
diff --git a/protos/perfetto/cloud_trace_processor/common.proto b/protos/perfetto/cloud_trace_processor/common.proto
new file mode 100644
index 0000000..f2fd5cb
--- /dev/null
+++ b/protos/perfetto/cloud_trace_processor/common.proto
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+enum TracePoolType {
+  TYPE_UNKNOWN = 0;
+
+  // Indicates that the trace pool can be accessed by more than one user. This
+  // implies the pool is "stateless" (i.e. TraceProcessor instances do not
+  // retain state between RPCs).
+  SHARED = 1;
+
+  // Indicates that the trace pool is only accessible by a single user at a
+  // time. This implies the pool is "stateful" (i.e. TraceProcessor instances
+  // retain state across RPCs).
+  DEDICATED = 2;
+}
diff --git a/protos/perfetto/cloud_trace_processor/orchestrator.proto b/protos/perfetto/cloud_trace_processor/orchestrator.proto
new file mode 100644
index 0000000..2f16e23
--- /dev/null
+++ b/protos/perfetto/cloud_trace_processor/orchestrator.proto
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+import "protos/perfetto/trace_processor/trace_processor.proto";
+import "protos/perfetto/cloud_trace_processor/common.proto";
+
+// RPC interface for a CloudTraceProcessor Orchestrator.
+//
+// Each CloudTraceProcessor instance has a single Orchestrator which is
+// responsible for receiving requests for loading and querying traces from
+// clients and shards these requests among a set of "Workers".
+service CloudTraceProcessorOrchestrator {
+  // Creates a TracePool with the specified arguments.
+  //
+  // A TracePool is a logical group of traces which can be addressed with a
+  // single id.
+  //
+  // Pools can be "shared" or "dedicated":
+  //  a) a shared pool has the trace processor instances backing the pool shared
+  //  among a group of users. This implicitly means that the pools are
+  //  "stateless" (i.e. do not preserve trace processor state between RPCs) as
+  //  the state of one user should not interfere with the state of another.
+  //  b) a dedicated pool belongs to a single user and can only be accessed
+  //  by that user. These pools are "stateful" i.e. preserve trace processor
+  //  state between RPCs.
+  rpc TracePoolCreate(TracePoolCreateArgs) returns (TracePoolCreateResponse);
+
+  // Changes the set of traces associated with the specified TracePool.
+  //
+  // If this operation completes successfully, any future requests to this pool
+  // shard will refer to this set of traces.
+  rpc TracePoolSetTraces(TracePoolSetTracesArgs)
+      returns (TracePoolSetTracesResponse);
+
+  // Executes a SQL query on the specified TracePool and returns a stream
+  // with each element being the response for executing the query on the
+  // associated trace.
+  //
+  // Note that each trace can return >1 result due to chunking of protos at the
+  // TraceProcessor::QueryResult level.
+  rpc TracePoolQuery(TracePoolQueryArgs)
+      returns (stream TracePoolQueryResponse);
+
+  // Destroys the TracePool with the specified id.
+  //
+  // Any future requests to this pool will return an error. However, the
+  // same pool id (if a named pool) can be used to create a new pool.
+  rpc TracePoolDestroy(TracePoolDestroyArgs) returns (TracePoolDestroyResponse);
+}
+
+// Request/Response for Orchestrator::TracePoolCreate.
+message TracePoolCreateArgs {
+  optional TracePoolType pool_type = 1;
+
+  // If |pool_type| == SHARED, the name which should be refer to the pool. This
+  // name will form part of |pool_id|.
+  optional string shared_pool_name = 2;
+}
+message TracePoolCreateResponse {
+  // The id of the pool which should be used to reference the pool in all future
+  // RPCs. For shared pools, this id is expected to be a stable transformation
+  // of |shared_pool_name|.
+  optional string pool_id = 1;
+}
+
+// Request/Response for Orchestrator::TracePoolSetTraces.
+message TracePoolSetTracesArgs {
+  optional string pool_id = 1;
+
+  // The list of traces which should be associated with this pool. The existing
+  // loaded trace list will be diffed against this list. Traces not present in
+  // this list and loaded will be unloaded while traces present in this list
+  // and unloaded will be loaded.
+  repeated string traces = 2;
+}
+message TracePoolSetTracesResponse {}
+
+// Request/Response for Orchestrator::TracePoolQuery.
+message TracePoolQueryArgs {
+  optional string pool_id = 1;
+  optional string sql_query = 2;
+}
+message TracePoolQueryResponse {
+  optional string trace = 1;
+  optional QueryResult result = 2;
+}
+
+// Request/Response for Orchestrator::TracePoolDestroy.
+message TracePoolDestroyArgs {
+  optional string pool_id = 1;
+}
+message TracePoolDestroyResponse {}
diff --git a/protos/perfetto/cloud_trace_processor/worker.proto b/protos/perfetto/cloud_trace_processor/worker.proto
new file mode 100644
index 0000000..407ef90
--- /dev/null
+++ b/protos/perfetto/cloud_trace_processor/worker.proto
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+import "protos/perfetto/trace_processor/trace_processor.proto";
+import "protos/perfetto/cloud_trace_processor/common.proto";
+
+// Interface for a CloudTraceProcessor "Worker".
+//
+// Workers are are owned by a |Orchestrator| who assigns groups of traces to
+// them (known as a PoolShards) and forwards any requests from end users.
+// Workers are reponsible for loading assigned traces with TraceProcessor and
+// executing the requests.
+service CloudTraceProcessorWorker {
+  // Creates a TracePoolShard which will be owned by this worker and returns
+  // whether it was successfully created.
+  //
+  // Orchestrators are responsible for handling groups of traces which the user
+  // has requested to be loaded: these are known as TracePools. The orchestrator
+  // then breaks these pools into pieces and shards them out to workers, each of
+  // which is known as a TracePoolShard.
+  //
+  // Thus, a TracePoolShard is unique identified by the tuple (worker, pool id).
+  rpc TracePoolShardCreate(TracePoolShardCreateArgs)
+      returns (TracePoolShardCreateResponse);
+
+  // Associates the provided list of traces to this TracePoolShard and returns
+  // a stream with each element indicating the successful load of one trace
+  // (which allows monitoring the progress of loads) or a terminal error if the
+  // assignment of any trace failed.
+  //
+  // If this operation completes successfully, any future requests to this pool
+  // shard will refer to this set of traces.
+  rpc TracePoolShardSetTraces(TracePoolShardSetTracesArgs)
+      returns (stream TracePoolShardSetTracesResponse);
+
+  // Executes a SQL query on the specified TracePoolShard and returns a stream
+  // with each element being the response for executing the query on the
+  // associated trace.
+  //
+  // Note that each trace can return >1 result due to chunking of protos at the
+  // TraceProcessor::QueryResult level.
+  rpc TracePoolShardQuery(TracePoolShardQueryArgs)
+      returns (stream TracePoolShardQueryResponse);
+
+  // Destroys the TracePoolShard with the specified id.
+  //
+  // Any future requests to this shard id will return an error. However, the
+  // same pool id can be used to create a new shard.
+  rpc TracePoolShardDestroy(TracePoolShardDestroyArgs)
+      returns (TracePoolShardDestroyResponse);
+}
+
+// Request/Response for Worker::TracePoolShardCreate.
+message TracePoolShardCreateArgs {
+  optional string pool_id = 1;
+  optional TracePoolType pool_type = 2;
+}
+message TracePoolShardCreateResponse {}
+
+// Request/Response for Worker::TracePoolShardSetTraces.
+message TracePoolShardSetTracesArgs {
+  optional string pool_id = 1;
+
+  // The list of traces which should be associated with this shard. The existing
+  // loaded trace list will be diffed against this list. Traces not present in
+  // this list and loaded will be unloaded while traces present in this list
+  // and unloaded will be loaded.
+  repeated string traces = 2;
+}
+message TracePoolShardSetTracesResponse {
+  optional string trace = 1;
+}
+
+// Request/Response for Worker::TracePoolShardQuery.
+message TracePoolShardQueryArgs {
+  optional string pool_id = 1;
+  optional string sql_query = 2;
+}
+message TracePoolShardQueryResponse {
+  optional string trace = 1;
+  optional QueryResult result = 2;
+}
+
+// Request/Response for Worker::TracePoolShardDestroy.
+message TracePoolShardDestroyArgs {
+  optional string pool_id = 1;
+}
+message TracePoolShardDestroyResponse {}
diff --git a/protos/perfetto/common/observable_events.proto b/protos/perfetto/common/observable_events.proto
index 0bde227..85767a6 100644
--- a/protos/perfetto/common/observable_events.proto
+++ b/protos/perfetto/common/observable_events.proto
@@ -35,6 +35,11 @@
     // Introduced in Android 11 (R).
     TYPE_ALL_DATA_SOURCES_STARTED = 2;
 
+    // When a tracing session has one or more triggers of type CLONE_SNAPSHOT
+    // and a matching trigger is hit, the service will send this notification to
+    // the consumer after |stop_delay_ms|.
+    TYPE_CLONE_TRIGGER_HIT = 4;
+
     // Note: internally these are used as OR flags. Next values: 4, 8, 16, ...
 
     // TODO(eseckler): Extend this for producer & data source registrations.
@@ -52,6 +57,15 @@
     optional DataSourceInstanceState state = 3;
   }
 
+  message CloneTriggerHit {
+    // The TracingSessionID of the original tracing session which had a
+    // CLONE_SNAPSHOT trigger defined. This is necessary just because the
+    // consumer has no idea of what is the TSID of its own tracing session and
+    // there is no other good way to plumb it.
+    optional int64 tracing_session_id = 1;
+  }
+
   repeated DataSourceInstanceStateChange instance_state_changes = 1;
   optional bool all_data_sources_started = 2;
+  optional CloneTriggerHit clone_trigger_hit = 3;
 }
diff --git a/protos/perfetto/common/tracing_service_capabilities.proto b/protos/perfetto/common/tracing_service_capabilities.proto
index 123f9bf..308f18d 100644
--- a/protos/perfetto/common/tracing_service_capabilities.proto
+++ b/protos/perfetto/common/tracing_service_capabilities.proto
@@ -35,4 +35,7 @@
   // Whether the service supports TraceConfig.output_path (for asking traced to
   // create the output file instead of passing a file descriptor).
   optional bool has_trace_config_output_path = 3;
+
+  // Whether the service supports CloneSession and CLONE_SNAPSHOT triggers.
+  optional bool has_clone_session = 4;
 }
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 7b1171f..85f1de7 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -833,6 +833,7 @@
 
   // If > 0 samples counters (see process_stats.proto) from
   // /proc/pid/status and oom_score_adj every X ms.
+  // It will also sample /proc/pid/smaps_rollup if scan_smaps_rollup = true.
   // This is required to be > 100ms to avoid excessive CPU usage.
   // TODO(primiano): add CPU cost for change this value.
   optional uint32 proc_stats_poll_ms = 4;
@@ -861,6 +862,10 @@
   // new fds opened after initially scanning a process will not be
   // recognized.
   optional bool resolve_process_fds = 9;
+
+  // If enabled memory stats from /proc/pid/smaps_rollup will be included
+  // in process stats.
+  optional bool scan_smaps_rollup = 10;
 }
 
 // End of protos/perfetto/config/process_stats/process_stats_config.proto
@@ -2700,7 +2705,7 @@
 // It contains the general config for the logging buffer(s) and the configs for
 // all the data source being enabled.
 //
-// Next id: 37.
+// Next id: 38.
 message TraceConfig {
   message BufferConfig {
     optional uint32 size_kb = 1;
@@ -2968,12 +2973,35 @@
       // consumer.
       STOP_TRACING = 2;
 
-      // NOTE: do not add new enum values here because of a subtle backward
-      // compat bug which might cause indefinite tracing on older versions of
-      // the service. See b/274931668 .
+      // When this mode is chosen, this causes a snapshot of the current tracing
+      // session to be created after |stop_delay_ms| while the current tracing
+      // session continues undisturbed (% an extra flush). This mode can be
+      // used only when the tracing session is handled by the "perfetto" cmdline
+      // client (which is true in 90% of cases). Part of the business logic
+      // necessary for this behavior, and ensuing file handling, lives in
+      // perfetto_cmd.cc . On other consumers, this causes only a notification
+      // of the trigger through a CloneTriggerHit ObservableEvent. The custom
+      // consumer is supposed to call CloneSession() itself after the event.
+      // Use use_clone_snapshot_if_available=true when targeting older versions
+      // of perfetto.
+      CLONE_SNAPSHOT = 3;
+
+      // NOTE: CLONE_SNAPSHOT should be used only when we targeting Android U+
+      // (14+) / Perfetto v34+. A bug in older versions of the tracing service
+      // might cause indefinitely long tracing sessions (see b/274931668).
     }
     optional TriggerMode trigger_mode = 1;
 
+    // This flag is really a workaround for b/274931668. This is needed only
+    // when deploying configs to different versions of the tracing service.
+    // When this is set to true this has the same effect of setting trigger_mode
+    // to CLONE_SNAPSHOT on newer versions of the service. This boolean has been
+    // introduced to allow to have configs that use CLONE_SNAPSHOT on newer
+    // versions of Android and fall back to STOP_TRACING on older versions where
+    // CLONE_SNAPSHOT did not exist.
+    // When using this flag, trigger_mode must be set to STOP_TRACING.
+    optional bool use_clone_snapshot_if_available = 4;
+
     message Trigger {
       // The producer must specify this name to activate the trigger.
       optional string name = 1;
@@ -2985,6 +3013,8 @@
 
       // After a trigger is received either in START_TRACING or STOP_TRACING
       // mode then the trace will end |stop_delay_ms| after triggering.
+      // In CLONE_SNAPSHOT mode, this is the delay between the trigger and the
+      // snapshot.
       // If |prefer_suspend_clock_for_duration| is set, the duration will be
       // based on wall-clock, counting also time in suspend.
       optional uint32 stop_delay_ms = 3;
@@ -3064,6 +3094,11 @@
   }
   optional CompressionType compression_type = 24;
 
+  // Use the legacy codepath that compresses from perfetto_cmd.cc instead of
+  // using the new codepath that compresses from tracing_service_impl.cc. This
+  // will be removed in the future.
+  optional bool compress_from_cli = 37;
+
   // Android-only. Not for general use. If set, saves the trace into an
   // incident. This field is read by perfetto_cmd, rather than the tracing
   // service. This field must be set when passing the --upload flag to
diff --git a/protos/perfetto/config/process_stats/process_stats_config.proto b/protos/perfetto/config/process_stats/process_stats_config.proto
index d71e7d3..239513f 100644
--- a/protos/perfetto/config/process_stats/process_stats_config.proto
+++ b/protos/perfetto/config/process_stats/process_stats_config.proto
@@ -41,6 +41,7 @@
 
   // If > 0 samples counters (see process_stats.proto) from
   // /proc/pid/status and oom_score_adj every X ms.
+  // It will also sample /proc/pid/smaps_rollup if scan_smaps_rollup = true.
   // This is required to be > 100ms to avoid excessive CPU usage.
   // TODO(primiano): add CPU cost for change this value.
   optional uint32 proc_stats_poll_ms = 4;
@@ -69,4 +70,8 @@
   // new fds opened after initially scanning a process will not be
   // recognized.
   optional bool resolve_process_fds = 9;
+
+  // If enabled memory stats from /proc/pid/smaps_rollup will be included
+  // in process stats.
+  optional bool scan_smaps_rollup = 10;
 }
diff --git a/protos/perfetto/config/trace_config.proto b/protos/perfetto/config/trace_config.proto
index 04e637c..4fe3ea9 100644
--- a/protos/perfetto/config/trace_config.proto
+++ b/protos/perfetto/config/trace_config.proto
@@ -26,7 +26,7 @@
 // It contains the general config for the logging buffer(s) and the configs for
 // all the data source being enabled.
 //
-// Next id: 37.
+// Next id: 38.
 message TraceConfig {
   message BufferConfig {
     optional uint32 size_kb = 1;
@@ -294,12 +294,35 @@
       // consumer.
       STOP_TRACING = 2;
 
-      // NOTE: do not add new enum values here because of a subtle backward
-      // compat bug which might cause indefinite tracing on older versions of
-      // the service. See b/274931668 .
+      // When this mode is chosen, this causes a snapshot of the current tracing
+      // session to be created after |stop_delay_ms| while the current tracing
+      // session continues undisturbed (% an extra flush). This mode can be
+      // used only when the tracing session is handled by the "perfetto" cmdline
+      // client (which is true in 90% of cases). Part of the business logic
+      // necessary for this behavior, and ensuing file handling, lives in
+      // perfetto_cmd.cc . On other consumers, this causes only a notification
+      // of the trigger through a CloneTriggerHit ObservableEvent. The custom
+      // consumer is supposed to call CloneSession() itself after the event.
+      // Use use_clone_snapshot_if_available=true when targeting older versions
+      // of perfetto.
+      CLONE_SNAPSHOT = 3;
+
+      // NOTE: CLONE_SNAPSHOT should be used only when we targeting Android U+
+      // (14+) / Perfetto v34+. A bug in older versions of the tracing service
+      // might cause indefinitely long tracing sessions (see b/274931668).
     }
     optional TriggerMode trigger_mode = 1;
 
+    // This flag is really a workaround for b/274931668. This is needed only
+    // when deploying configs to different versions of the tracing service.
+    // When this is set to true this has the same effect of setting trigger_mode
+    // to CLONE_SNAPSHOT on newer versions of the service. This boolean has been
+    // introduced to allow to have configs that use CLONE_SNAPSHOT on newer
+    // versions of Android and fall back to STOP_TRACING on older versions where
+    // CLONE_SNAPSHOT did not exist.
+    // When using this flag, trigger_mode must be set to STOP_TRACING.
+    optional bool use_clone_snapshot_if_available = 4;
+
     message Trigger {
       // The producer must specify this name to activate the trigger.
       optional string name = 1;
@@ -311,6 +334,8 @@
 
       // After a trigger is received either in START_TRACING or STOP_TRACING
       // mode then the trace will end |stop_delay_ms| after triggering.
+      // In CLONE_SNAPSHOT mode, this is the delay between the trigger and the
+      // snapshot.
       // If |prefer_suspend_clock_for_duration| is set, the duration will be
       // based on wall-clock, counting also time in suspend.
       optional uint32 stop_delay_ms = 3;
@@ -390,6 +415,11 @@
   }
   optional CompressionType compression_type = 24;
 
+  // Use the legacy codepath that compresses from perfetto_cmd.cc instead of
+  // using the new codepath that compresses from tracing_service_impl.cc. This
+  // will be removed in the future.
+  optional bool compress_from_cli = 37;
+
   // Android-only. Not for general use. If set, saves the trace into an
   // incident. This field is read by perfetto_cmd, rather than the tracing
   // service. This field must be set when passing the --upload flag to
diff --git a/protos/perfetto/ipc/consumer_port.proto b/protos/perfetto/ipc/consumer_port.proto
index d5a4025..2fdc919 100644
--- a/protos/perfetto/ipc/consumer_port.proto
+++ b/protos/perfetto/ipc/consumer_port.proto
@@ -289,4 +289,8 @@
   // the details about the failure.
   optional bool success = 1;
   optional string error = 2;
+
+  // The UUID of the cloned session.
+  optional int64 uuid_msb = 3;
+  optional int64 uuid_lsb = 4;
 }
diff --git a/protos/perfetto/metrics/android/binder_metric.proto b/protos/perfetto/metrics/android/binder_metric.proto
index 5c4e66f..36c1abb 100644
--- a/protos/perfetto/metrics/android/binder_metric.proto
+++ b/protos/perfetto/metrics/android/binder_metric.proto
@@ -56,6 +56,9 @@
 
     optional uint32 client_tid = 15;
     optional uint32 server_tid = 16;
+
+    optional uint32 client_pid = 17;
+    optional uint32 server_pid = 18;
   }
 
   message ThreadStateBreakdown {
diff --git a/protos/perfetto/metrics/android/jank_cuj_metric.proto b/protos/perfetto/metrics/android/jank_cuj_metric.proto
index fa3a7b1..007554f 100644
--- a/protos/perfetto/metrics/android/jank_cuj_metric.proto
+++ b/protos/perfetto/metrics/android/jank_cuj_metric.proto
@@ -61,7 +61,7 @@
     optional string layer_name = 11;
   }
 
-  // Next id: 8
+  // Next id: 10
   message Frame {
     // Index of the frame within the single user journey.
     optional int64 frame_number = 1;
@@ -79,9 +79,17 @@
 
     // Whether SF missed the frame deadline.
     optional bool sf_missed = 6;
+
+    // Whether the SF callback missed before emitting jank metrics.
+    // SF callback is used to get the jank classification.
+    optional bool sf_callback_missed = 8;
+
+    // Whether the HWUI callback missed before emitting jank metrics.
+    // HWUI callback is used to get the frame duration.
+    optional bool hwui_callback_missed = 9;
   }
 
-  // Next id: 16
+  // Next id: 18
   message Metrics {
     // Overall number of frames within the CUJ.
     optional int64 total_frames = 1;
@@ -137,5 +145,11 @@
     // P99 frame duration in milliseconds.
     // Not available in counter_metrics.
     optional double frame_dur_ms_p99 = 15;
+
+    // Number of frames with missed SF callback.
+    optional int64 sf_callback_missed_frames = 16;
+
+    // Number of frames with missed HWUI callback.
+    optional int64 hwui_callback_missed_frames = 17;
   }
 }
diff --git a/protos/perfetto/metrics/android/java_heap_stats.proto b/protos/perfetto/metrics/android/java_heap_stats.proto
index 2579888..10a0b8b 100644
--- a/protos/perfetto/metrics/android/java_heap_stats.proto
+++ b/protos/perfetto/metrics/android/java_heap_stats.proto
@@ -26,7 +26,7 @@
     optional int64 obj_count = 3;
   }
 
-  // Next id: 10
+  // Next id: 11
   message Sample {
     optional int64 ts = 1;
     // Size of the Java heap in bytes
@@ -45,6 +45,8 @@
 
     // ART root objects
     repeated HeapRoots roots = 7;
+    // OOM adjustment score
+    optional int64 oom_score_adj = 10;
   }
 
   // Heap stats per process. One sample per dump (can be > 1 if continuous
diff --git a/protos/perfetto/metrics/android/process_metadata.proto b/protos/perfetto/metrics/android/process_metadata.proto
index fd7fe7b..fa766be 100644
--- a/protos/perfetto/metrics/android/process_metadata.proto
+++ b/protos/perfetto/metrics/android/process_metadata.proto
@@ -44,5 +44,8 @@
   // https://developer.android.com/guide/topics/manifest/manifest-element#uid
   repeated Package packages_for_uid = 8;
 
+  // Pid of the process name.
+  optional int64 pid = 9;
+
   reserved 3, 4, 5, 6;
 }
diff --git a/protos/perfetto/metrics/android/startup_metric.proto b/protos/perfetto/metrics/android/startup_metric.proto
index 916a212..67c9044 100644
--- a/protos/perfetto/metrics/android/startup_metric.proto
+++ b/protos/perfetto/metrics/android/startup_metric.proto
@@ -47,7 +47,7 @@
 
   // Timing information spanning the intent received by the
   // activity manager to the first frame drawn.
-  // Next id: 33.
+  // Next id: 35.
   message ToFirstFrame {
     // The duration between the intent received and first frame.
     optional int64 dur_ns = 1;
@@ -117,6 +117,14 @@
     // |time_lock_contention_thread_main|.
     optional Slice time_monitor_contention_thread_main = 32;
 
+    // Time spent in opening dex files on the main thread of the process
+    // being started up.
+    optional Slice time_dex_open_thread_main = 33;
+
+    // Time spent in dlopening .so files on the main thread of the process
+    // being started up.
+    optional Slice time_dlopen_thread_main = 34;
+
     // Removed: was other_process_to_activity_cpu_ratio.
     reserved 12;
 
@@ -216,7 +224,7 @@
     optional int64 dex2oat_dur_ns = 7;
   }
 
-  // Next id: 20
+  // Next id: 21
   message Startup {
     // Random id uniquely identifying an app startup in this trace.
     optional uint32 startup_id = 1;
@@ -272,6 +280,9 @@
     // Contains information about the class verification.
     repeated VerifyClass verify_class = 19;
 
+    // Contains the dlopen file names.
+    repeated string dlopen_file = 20;
+
     // Package name of startups running concurrent to the launch.
     repeated string startup_concurrent_to_launch = 18;
 
diff --git a/protos/perfetto/metrics/android/surfaceflinger.proto b/protos/perfetto/metrics/android/surfaceflinger.proto
index 3cc2c9e..1f81827 100644
--- a/protos/perfetto/metrics/android/surfaceflinger.proto
+++ b/protos/perfetto/metrics/android/surfaceflinger.proto
@@ -54,4 +54,32 @@
   // This also equals to the total duration of
   // "waiting for GPU completion <fence_num>" in SurfaceFlinger.
   optional double total_non_empty_gpu_waiting_dur_ms = 9;
+
+  message MetricsPerDisplay {
+    // Display ID in SF
+    optional string display_id = 1;
+
+    // Counts the number of missed frames in the trace.
+    optional uint32 missed_frames = 2;
+
+    // Counts the number of missed HWC frames in the trace.
+    optional uint32 missed_hwc_frames = 3;
+
+    // Counts the number of missed GPU frames in the trace.
+    optional uint32 missed_gpu_frames = 4;
+
+    // Calculate the number of missed frames divided by
+    // total frames
+    optional double missed_frame_rate = 5;
+
+    // Calculate the number of missed HWC frames divided by
+    // total HWC frames
+    optional double missed_hwc_frame_rate = 6;
+
+    // Calculate the number of missed GPU frames divided by
+    // total GPU frames
+    optional double missed_gpu_frame_rate = 7;
+  }
+
+  repeated MetricsPerDisplay metrics_per_display = 10;
 }
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 2fc2bae..5fb597d 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -42,6 +42,9 @@
   // https://developer.android.com/guide/topics/manifest/manifest-element#uid
   repeated Package packages_for_uid = 8;
 
+  // Pid of the process name.
+  optional int64 pid = 9;
+
   reserved 3, 4, 5, 6;
 }
 
@@ -222,6 +225,9 @@
 
     optional uint32 client_tid = 15;
     optional uint32 server_tid = 16;
+
+    optional uint32 client_pid = 17;
+    optional uint32 server_pid = 18;
   }
 
   message ThreadStateBreakdown {
@@ -880,7 +886,7 @@
     optional string layer_name = 11;
   }
 
-  // Next id: 8
+  // Next id: 10
   message Frame {
     // Index of the frame within the single user journey.
     optional int64 frame_number = 1;
@@ -898,9 +904,17 @@
 
     // Whether SF missed the frame deadline.
     optional bool sf_missed = 6;
+
+    // Whether the SF callback missed before emitting jank metrics.
+    // SF callback is used to get the jank classification.
+    optional bool sf_callback_missed = 8;
+
+    // Whether the HWUI callback missed before emitting jank metrics.
+    // HWUI callback is used to get the frame duration.
+    optional bool hwui_callback_missed = 9;
   }
 
-  // Next id: 16
+  // Next id: 18
   message Metrics {
     // Overall number of frames within the CUJ.
     optional int64 total_frames = 1;
@@ -956,6 +970,12 @@
     // P99 frame duration in milliseconds.
     // Not available in counter_metrics.
     optional double frame_dur_ms_p99 = 15;
+
+    // Number of frames with missed SF callback.
+    optional int64 sf_callback_missed_frames = 16;
+
+    // Number of frames with missed HWUI callback.
+    optional int64 hwui_callback_missed_frames = 17;
   }
 }
 
@@ -1005,7 +1025,7 @@
     optional int64 obj_count = 3;
   }
 
-  // Next id: 10
+  // Next id: 11
   message Sample {
     optional int64 ts = 1;
     // Size of the Java heap in bytes
@@ -1024,6 +1044,8 @@
 
     // ART root objects
     repeated HeapRoots roots = 7;
+    // OOM adjustment score
+    optional int64 oom_score_adj = 10;
   }
 
   // Heap stats per process. One sample per dump (can be > 1 if continuous
@@ -1577,7 +1599,7 @@
 
   // Timing information spanning the intent received by the
   // activity manager to the first frame drawn.
-  // Next id: 33.
+  // Next id: 35.
   message ToFirstFrame {
     // The duration between the intent received and first frame.
     optional int64 dur_ns = 1;
@@ -1647,6 +1669,14 @@
     // |time_lock_contention_thread_main|.
     optional Slice time_monitor_contention_thread_main = 32;
 
+    // Time spent in opening dex files on the main thread of the process
+    // being started up.
+    optional Slice time_dex_open_thread_main = 33;
+
+    // Time spent in dlopening .so files on the main thread of the process
+    // being started up.
+    optional Slice time_dlopen_thread_main = 34;
+
     // Removed: was other_process_to_activity_cpu_ratio.
     reserved 12;
 
@@ -1746,7 +1776,7 @@
     optional int64 dex2oat_dur_ns = 7;
   }
 
-  // Next id: 20
+  // Next id: 21
   message Startup {
     // Random id uniquely identifying an app startup in this trace.
     optional uint32 startup_id = 1;
@@ -1802,6 +1832,9 @@
     // Contains information about the class verification.
     repeated VerifyClass verify_class = 19;
 
+    // Contains the dlopen file names.
+    repeated string dlopen_file = 20;
+
     // Package name of startups running concurrent to the launch.
     repeated string startup_concurrent_to_launch = 18;
 
@@ -1860,6 +1893,34 @@
   // This also equals to the total duration of
   // "waiting for GPU completion <fence_num>" in SurfaceFlinger.
   optional double total_non_empty_gpu_waiting_dur_ms = 9;
+
+  message MetricsPerDisplay {
+    // Display ID in SF
+    optional string display_id = 1;
+
+    // Counts the number of missed frames in the trace.
+    optional uint32 missed_frames = 2;
+
+    // Counts the number of missed HWC frames in the trace.
+    optional uint32 missed_hwc_frames = 3;
+
+    // Counts the number of missed GPU frames in the trace.
+    optional uint32 missed_gpu_frames = 4;
+
+    // Calculate the number of missed frames divided by
+    // total frames
+    optional double missed_frame_rate = 5;
+
+    // Calculate the number of missed HWC frames divided by
+    // total HWC frames
+    optional double missed_hwc_frame_rate = 6;
+
+    // Calculate the number of missed GPU frames divided by
+    // total GPU frames
+    optional double missed_gpu_frame_rate = 7;
+  }
+
+  repeated MetricsPerDisplay metrics_per_display = 10;
 }
 
 // End of protos/perfetto/metrics/android/surfaceflinger.proto
diff --git a/protos/perfetto/metrics/webview/webview_jank_approximation.proto b/protos/perfetto/metrics/webview/webview_jank_approximation.proto
index 0d01a77..cdf7299 100644
--- a/protos/perfetto/metrics/webview/webview_jank_approximation.proto
+++ b/protos/perfetto/metrics/webview/webview_jank_approximation.proto
@@ -19,9 +19,9 @@
 package perfetto.protos;
 
 message WebViewJankApproximation {
-  required int32 webview_janks = 1;
-  required int32 webview_janks_without_startup = 2;
-  required int32 webview_app_janks = 3;
-  required int32 webview_total_janks = 4;
-  required int32 total_janks = 5;
+  optional int32 webview_janks = 1;
+  optional int32 webview_janks_without_startup = 2;
+  optional int32 webview_app_janks = 3;
+  optional int32 webview_total_janks = 4;
+  optional int32 total_janks = 5;
 }
diff --git a/protos/perfetto/trace/ftrace/ftrace_event.proto b/protos/perfetto/trace/ftrace/ftrace_event.proto
index b86ad9f..88d3641 100644
--- a/protos/perfetto/trace/ftrace/ftrace_event.proto
+++ b/protos/perfetto/trace/ftrace/ftrace_event.proto
@@ -595,5 +595,7 @@
     HostSmcFtraceEvent host_smc = 479;
     HostMemAbortFtraceEvent host_mem_abort = 480;
     SuspendResumeMinimalFtraceEvent suspend_resume_minimal = 481;
+    MaliMaliCSFINTERRUPTSTARTFtraceEvent mali_mali_CSF_INTERRUPT_START = 482;
+    MaliMaliCSFINTERRUPTENDFtraceEvent mali_mali_CSF_INTERRUPT_END = 483;
   }
 }
diff --git a/protos/perfetto/trace/ftrace/mali.proto b/protos/perfetto/trace/ftrace/mali.proto
index 0c59cd4..9d67166 100644
--- a/protos/perfetto/trace/ftrace/mali.proto
+++ b/protos/perfetto/trace/ftrace/mali.proto
@@ -53,3 +53,13 @@
   optional uint32 kctx_id = 4;
   optional uint32 id = 5;
 }
+message MaliMaliCSFINTERRUPTSTARTFtraceEvent {
+  optional int32 kctx_tgid = 1;
+  optional uint32 kctx_id = 2;
+  optional uint64 info_val = 3;
+}
+message MaliMaliCSFINTERRUPTENDFtraceEvent {
+  optional int32 kctx_tgid = 1;
+  optional uint32 kctx_id = 2;
+  optional uint64 info_val = 3;
+}
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 7b9279f..e886311 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -833,6 +833,7 @@
 
   // If > 0 samples counters (see process_stats.proto) from
   // /proc/pid/status and oom_score_adj every X ms.
+  // It will also sample /proc/pid/smaps_rollup if scan_smaps_rollup = true.
   // This is required to be > 100ms to avoid excessive CPU usage.
   // TODO(primiano): add CPU cost for change this value.
   optional uint32 proc_stats_poll_ms = 4;
@@ -861,6 +862,10 @@
   // new fds opened after initially scanning a process will not be
   // recognized.
   optional bool resolve_process_fds = 9;
+
+  // If enabled memory stats from /proc/pid/smaps_rollup will be included
+  // in process stats.
+  optional bool scan_smaps_rollup = 10;
 }
 
 // End of protos/perfetto/config/process_stats/process_stats_config.proto
@@ -2700,7 +2705,7 @@
 // It contains the general config for the logging buffer(s) and the configs for
 // all the data source being enabled.
 //
-// Next id: 37.
+// Next id: 38.
 message TraceConfig {
   message BufferConfig {
     optional uint32 size_kb = 1;
@@ -2968,12 +2973,35 @@
       // consumer.
       STOP_TRACING = 2;
 
-      // NOTE: do not add new enum values here because of a subtle backward
-      // compat bug which might cause indefinite tracing on older versions of
-      // the service. See b/274931668 .
+      // When this mode is chosen, this causes a snapshot of the current tracing
+      // session to be created after |stop_delay_ms| while the current tracing
+      // session continues undisturbed (% an extra flush). This mode can be
+      // used only when the tracing session is handled by the "perfetto" cmdline
+      // client (which is true in 90% of cases). Part of the business logic
+      // necessary for this behavior, and ensuing file handling, lives in
+      // perfetto_cmd.cc . On other consumers, this causes only a notification
+      // of the trigger through a CloneTriggerHit ObservableEvent. The custom
+      // consumer is supposed to call CloneSession() itself after the event.
+      // Use use_clone_snapshot_if_available=true when targeting older versions
+      // of perfetto.
+      CLONE_SNAPSHOT = 3;
+
+      // NOTE: CLONE_SNAPSHOT should be used only when we targeting Android U+
+      // (14+) / Perfetto v34+. A bug in older versions of the tracing service
+      // might cause indefinitely long tracing sessions (see b/274931668).
     }
     optional TriggerMode trigger_mode = 1;
 
+    // This flag is really a workaround for b/274931668. This is needed only
+    // when deploying configs to different versions of the tracing service.
+    // When this is set to true this has the same effect of setting trigger_mode
+    // to CLONE_SNAPSHOT on newer versions of the service. This boolean has been
+    // introduced to allow to have configs that use CLONE_SNAPSHOT on newer
+    // versions of Android and fall back to STOP_TRACING on older versions where
+    // CLONE_SNAPSHOT did not exist.
+    // When using this flag, trigger_mode must be set to STOP_TRACING.
+    optional bool use_clone_snapshot_if_available = 4;
+
     message Trigger {
       // The producer must specify this name to activate the trigger.
       optional string name = 1;
@@ -2985,6 +3013,8 @@
 
       // After a trigger is received either in START_TRACING or STOP_TRACING
       // mode then the trace will end |stop_delay_ms| after triggering.
+      // In CLONE_SNAPSHOT mode, this is the delay between the trigger and the
+      // snapshot.
       // If |prefer_suspend_clock_for_duration| is set, the duration will be
       // based on wall-clock, counting also time in suspend.
       optional uint32 stop_delay_ms = 3;
@@ -3064,6 +3094,11 @@
   }
   optional CompressionType compression_type = 24;
 
+  // Use the legacy codepath that compresses from perfetto_cmd.cc instead of
+  // using the new codepath that compresses from tracing_service_impl.cc. This
+  // will be removed in the future.
+  optional bool compress_from_cli = 37;
+
   // Android-only. Not for general use. If set, saves the trace into an
   // incident. This field is read by perfetto_cmd, rather than the tracing
   // service. This field must be set when passing the --upload flag to
@@ -6776,6 +6811,16 @@
   optional uint32 kctx_id = 4;
   optional uint32 id = 5;
 }
+message MaliMaliCSFINTERRUPTSTARTFtraceEvent {
+  optional int32 kctx_tgid = 1;
+  optional uint32 kctx_id = 2;
+  optional uint64 info_val = 3;
+}
+message MaliMaliCSFINTERRUPTENDFtraceEvent {
+  optional int32 kctx_tgid = 1;
+  optional uint32 kctx_id = 2;
+  optional uint64 info_val = 3;
+}
 
 // End of protos/perfetto/trace/ftrace/mali.proto
 
@@ -8279,6 +8324,8 @@
     HostSmcFtraceEvent host_smc = 479;
     HostMemAbortFtraceEvent host_mem_abort = 480;
     SuspendResumeMinimalFtraceEvent suspend_resume_minimal = 481;
+    MaliMaliCSFINTERRUPTSTARTFtraceEvent mali_mali_CSF_INTERRUPT_START = 482;
+    MaliMaliCSFINTERRUPTENDFtraceEvent mali_mali_CSF_INTERRUPT_END = 483;
   }
 }
 
@@ -11204,6 +11251,13 @@
     optional uint32 chrome_peak_resident_set_kb = 14;
 
     repeated FDInfo fds = 15;
+
+    // These fields are set only when scan_smaps_rollup=true
+    optional uint64 smr_rss_kb = 16;
+    optional uint64 smr_pss_kb = 17;
+    optional uint64 smr_pss_anon_kb = 18;
+    optional uint64 smr_pss_file_kb = 19;
+    optional uint64 smr_pss_shmem_kb = 20;
   }
   repeated Process processes = 1;
 
@@ -11290,7 +11344,7 @@
 // Deliberate empty message. See comment on StatsdAtom#atom below.
 message Atom {}
 
-// One or more statsd atoms. Ideally this should continue to match:
+// One or more statsd atoms. This must continue to match:
 // perfetto/protos/third_party/statsd/shell_data.proto
 // So that we can efficiently add data from statsd directly to the
 // trace.
diff --git a/protos/perfetto/trace/ps/process_stats.proto b/protos/perfetto/trace/ps/process_stats.proto
index 4ac0c40..03759b44 100644
--- a/protos/perfetto/trace/ps/process_stats.proto
+++ b/protos/perfetto/trace/ps/process_stats.proto
@@ -76,6 +76,13 @@
     optional uint32 chrome_peak_resident_set_kb = 14;
 
     repeated FDInfo fds = 15;
+
+    // These fields are set only when scan_smaps_rollup=true
+    optional uint64 smr_rss_kb = 16;
+    optional uint64 smr_pss_kb = 17;
+    optional uint64 smr_pss_anon_kb = 18;
+    optional uint64 smr_pss_file_kb = 19;
+    optional uint64 smr_pss_shmem_kb = 20;
   }
   repeated Process processes = 1;
 
diff --git a/protos/perfetto/trace/statsd/statsd_atom.proto b/protos/perfetto/trace/statsd/statsd_atom.proto
index 026766d..62ca960 100644
--- a/protos/perfetto/trace/statsd/statsd_atom.proto
+++ b/protos/perfetto/trace/statsd/statsd_atom.proto
@@ -20,7 +20,7 @@
 // Deliberate empty message. See comment on StatsdAtom#atom below.
 message Atom {}
 
-// One or more statsd atoms. Ideally this should continue to match:
+// One or more statsd atoms. This must continue to match:
 // perfetto/protos/third_party/statsd/shell_data.proto
 // So that we can efficiently add data from statsd directly to the
 // trace.
diff --git a/protos/perfetto/trace_processor/BUILD.gn b/protos/perfetto/trace_processor/BUILD.gn
index 5e2f376..2f6cb71 100644
--- a/protos/perfetto/trace_processor/BUILD.gn
+++ b/protos/perfetto/trace_processor/BUILD.gn
@@ -41,10 +41,3 @@
   ]
   sources = [ "metrics_impl.proto" ]
 }
-
-if (enable_perfetto_grpc) {
-  perfetto_grpc_library("cloud_trace_processor_grpc") {
-    deps = [ ":lite" ]
-    sources = [ "cloud_trace_processor.proto" ]
-  }
-}
diff --git a/protos/perfetto/trace_processor/cloud_trace_processor.proto b/protos/perfetto/trace_processor/cloud_trace_processor.proto
deleted file mode 100644
index b009fe3..0000000
--- a/protos/perfetto/trace_processor/cloud_trace_processor.proto
+++ /dev/null
@@ -1,21 +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.
- */
-
-syntax = "proto2";
-
-package perfetto.protos;
-
-service CloudTraceProcessorWorkerService {}
\ No newline at end of file
diff --git a/protos/perfetto/trace_processor/proto_files.gni b/protos/perfetto/trace_processor/proto_files.gni
index 5f202b2..f211050 100644
--- a/protos/perfetto/trace_processor/proto_files.gni
+++ b/protos/perfetto/trace_processor/proto_files.gni
@@ -15,8 +15,7 @@
 # This variable is used both by ./BUILD.gn (for the C++ proto codegen) and by
 # //ui/BUIlD.gn (for the TypeScript/JS proto codegen).
 trace_processor_protos = [
-  "cloud_trace_processor",
-  "trace_processor",
   "metatrace_categories",
   "stack",
+  "trace_processor",
 ]
diff --git a/protos/third_party/CHROMIUM_OWNERS b/protos/third_party/CHROMIUM_OWNERS
index 7cc656e..826302d 100644
--- a/protos/third_party/CHROMIUM_OWNERS
+++ b/protos/third_party/CHROMIUM_OWNERS
@@ -14,3 +14,7 @@
 nuskos@google.com
 oksamyt@google.com
 kartarsingh@google.com
+jkoshy@google.com
+amanvr@google.com
+rasikan@google.com
+violettfaid@google.com
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index f19c1cf..cce5e76 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -512,6 +512,12 @@
   // The ID of the BrowsingInstance that the BrowsingContextState belongs to.
   optional int32 browsing_instance_id = 1;
 
+  // The ID of the CoopRelatedGroup that the BrowsingContextState belongs to.
+  optional int32 coop_related_group_id = 2 [deprecated = true];
+
+  // The token of the CoopRelatedGroup that the BrowsingContextState belongs to.
+  optional string coop_related_group_token = 3;
+
   // Additional untyped debug information associated with this
   // FrameTreeNode, populated via TracedProto::AddDebugAnnotations API.
   repeated DebugAnnotation debug_annotations = 99;
@@ -734,6 +740,7 @@
     TASK_TYPE_INTERNAL_NAVIGATION_CANCELLATION = 80;
     TASK_TYPE_LOW_PRIORITY_SCRIPT_EXECUTION = 81;
     TASK_TYPE_STORAGE = 82;
+    TASK_TYPE_NETWORKING_UNFREEZABLE_IMAGE_LOADING = 83;
   }
 
   enum FrameType {
@@ -863,6 +870,7 @@
     HIGH_PRIORITY_CONTINUATION = 8;
     NORMAL_PRIORITY_CONTINUATION = 9;
     LOW_PRIORITY_CONTINUATION = 10;
+    EXTREMELY_HIGH_PRIORITY = 11;
   }
 
   enum QueueName {
@@ -1031,11 +1039,25 @@
     SERVICE_WORKER = 5;
   }
 
+  // Definition of world type.
+  enum WorldType {
+    WORLD_UNKNOWN = 0;
+    WORLD_MAIN = 1;
+    WORLD_ISOLATED = 2;
+    WORLD_INSPECTOR_ISOLATED = 3;
+    WORLD_REG_EXP = 4;
+    WORLD_FOR_V8_CONTEXT_SNAPSHOT_NON_MAIN = 5;
+    WORLD_WORKER = 6;
+    WORLD_SHADOW_REALM = 7;
+  }
+
   optional ContextType type = 1;
   // Contains url of frame or worker.
   optional string url = 2;
   // The origin of the execution context.
   optional string origin = 3;
+  // The world type of the execution context.
+  optional WorldType world_type = 4;
 }
 
 // Serializes the blink::SourceLocation object.
@@ -1079,10 +1101,28 @@
     // similar to "Navigator.languages.get".
     optional string identifier = 1;
     repeated JSFunctionArgument func_arguments = 2;
+
+    // Deprecated in favour of outer source_location.
     optional BlinkSourceLocation source_location = 3;
   }
   optional BlinkExecutionContext execution_context = 1;
   optional CalledJsApi called_api = 2;
+  optional BlinkSourceLocation source_location = 3;
+
+  // Describes lookup of a font.
+  message FontLookup {
+    enum FontLookupType {
+      FONT_LOOKUP_UNKNOWN_TYPE = 0;
+      FONT_LOOKUP_UNIQUE_OR_FAMILY_NAME = 1;
+      FONT_LOOKUP_UNIQUE_NAME_ONLY = 2;
+    }
+    optional FontLookupType type = 1;
+    optional string name = 2;
+    optional uint64 weight = 3;
+    optional uint64 width = 4;
+    optional uint64 slope = 5;
+  }
+  optional FontLookup font_lookup = 4;
 }
 
 // Contains information about a tab switch measurement.
diff --git a/protos/third_party/statsd/shell_data.proto b/protos/third_party/statsd/shell_data.proto
index e4c31bd..64551ea 100644
--- a/protos/third_party/statsd/shell_data.proto
+++ b/protos/third_party/statsd/shell_data.proto
@@ -20,8 +20,8 @@
 
 // This is a manual import of ShellData:
 // https://cs.android.com/android/platform/superproject/+/master:packages/modules/StatsD/statsd/src/shell/shell_data.proto;l=27;drc=d2e51ecdf08753688fb889b657dcba60adb994f3
-
+// This must exactly match perfetto.protos.StatsdAtom.
 message ShellData {
   repeated bytes atom = 1;
-  repeated int64 timestamp_nanos = 2 [packed = true];
+  repeated int64 timestamp_nanos = 2;
 }
diff --git a/python/generators/stdlib_docs/parse.py b/python/generators/stdlib_docs/parse.py
index 79f4cfe..bdef717 100644
--- a/python/generators/stdlib_docs/parse.py
+++ b/python/generators/stdlib_docs/parse.py
@@ -42,6 +42,8 @@
     m = re.match(Pattern['column'], line)
     if last_col:
       cols[last_col] = ' '.join(last_desc)
+    if not m:
+      print(f'Expected line {line} to match @column format', file=sys.stderr)
     last_col, last_desc = m.group(1), [m.group(2)]
 
   cols[last_col] = ' '.join(last_desc)
@@ -79,6 +81,8 @@
       desc.append(get_text(line))
 
     m = re.match(Pattern['return_arg'], line)
+    if not m:
+      print(f'Expected line {line} to match @ret format', file=sys.stderr)
     ret_type, desc = m.group(1), [m.group(2)]
   return (ret_type, ' '.join(desc))
 
diff --git a/python/generators/trace_processor_table/public.py b/python/generators/trace_processor_table/public.py
index abd0aec..c8bdb0b 100644
--- a/python/generators/trace_processor_table/public.py
+++ b/python/generators/trace_processor_table/public.py
@@ -119,6 +119,8 @@
   Representation of of a C++ table.
 
   Attributes:
+    python_module: Path to the Python module this table is defined in. Always
+    pass __file__.
     class_name: Name of the C++ table class.
     sql_name: Name of the table in SQL.
     columns: The columns in this table.
@@ -129,6 +131,7 @@
     specified table.
     wrapping_sql_view: See |WrappingSqlView|.
   """
+  python_module: str
   class_name: str
   sql_name: str
   columns: List[Column]
diff --git a/python/generators/trace_processor_table/serialize.py b/python/generators/trace_processor_table/serialize.py
index 0a8715c..1b498c3 100644
--- a/python/generators/trace_processor_table/serialize.py
+++ b/python/generators/trace_processor_table/serialize.py
@@ -19,6 +19,8 @@
 from python.generators.trace_processor_table.public import ColumnFlag
 from python.generators.trace_processor_table.util import ParsedTable
 from python.generators.trace_processor_table.util import ParsedColumn
+from python.generators.trace_processor_table.util import parse_type
+from python.generators.trace_processor_table.util import typed_column_type
 
 
 class ColumnSerializer:
@@ -30,8 +32,9 @@
     self.col = self.parsed_col.column
     self.name = self.col.name
     self.flags = self.col.flags
-    self.typed_column_type = table.typed_column_type(self.parsed_col)
-    self.cpp_type = table.parse_type(self.col.type).cpp_type_with_optionality()
+    self.typed_column_type = typed_column_type(table.table, self.parsed_col)
+    self.cpp_type = parse_type(table.table,
+                               self.col.type).cpp_type_with_optionality()
 
     self.is_implicit_id = self.parsed_col.is_implicit_id
     self.is_implicit_type = self.parsed_col.is_implicit_type
@@ -114,7 +117,7 @@
     return f'''
     columns_.emplace_back("{self.name}", &{self.name}_, ColumnFlag::{self.name},
                           this, static_cast<uint32_t>(columns_.size()),
-                          overlay_idx);
+                          olay_idx);
     '''
 
   def shrink_to_fit(self) -> Optional[str]:
@@ -180,11 +183,11 @@
     if self.is_implicit_id or self.is_implicit_type:
       return None
     return f'''
-      schema.columns.emplace_back(Table::Schema::Column{{
-          "{self.name}", ColumnType::{self.name}::SqlValueType(), false,
-          {str(ColumnFlag.SORTED in self.flags).lower()},
-          {str(ColumnFlag.HIDDEN in self.flags).lower()},
-          {str(ColumnFlag.SET_ID in self.flags).lower()}}});
+    schema.columns.emplace_back(Table::Schema::Column{{
+        "{self.name}", ColumnType::{self.name}::SqlValueType(), false,
+        {str(ColumnFlag.SORTED in self.flags).lower()},
+        {str(ColumnFlag.HIDDEN in self.flags).lower()},
+        {str(ColumnFlag.SET_ID in self.flags).lower()}}});
     '''
 
   def row_eq(self) -> Optional[str]:
@@ -192,6 +195,42 @@
       return None
     return f'ColumnType::{self.name}::Equals({self.name}, other.{self.name})'
 
+  def extend_parent_param(self) -> Optional[str]:
+    if self.is_implicit_id or self.is_implicit_type:
+      return None
+    if self.is_ancestor:
+      return None
+    return f'ColumnStorage<ColumnType::{self.name}::stored_type> {self.name}'
+
+  def extend_parent_param_arg(self) -> Optional[str]:
+    if self.is_implicit_id or self.is_implicit_type:
+      return None
+    if self.is_ancestor:
+      return None
+    return f'std::move({self.name})'
+
+  def static_assert_flags(self) -> Optional[str]:
+    if self.is_implicit_id or self.is_implicit_type:
+      return None
+    if self.is_ancestor:
+      return None
+    return f'''
+      static_assert(
+        Column::IsFlagsAndTypeValid<ColumnType::{self.name}::stored_type>(
+          ColumnFlag::{self.name}),
+        "Column type and flag combination is not valid");
+    '''
+
+  def extend_nullable_vector(self) -> Optional[str]:
+    if self.is_implicit_id or self.is_implicit_type:
+      return None
+    if self.is_ancestor:
+      return None
+    return f'''
+    PERFETTO_DCHECK({self.name}.size() == parent_overlay.size());
+    {self.name}_ = std::move({self.name});
+    '''
+
 
 class TableSerializer(object):
   """Functions for seralizing a single Table into C++."""
@@ -243,12 +282,13 @@
         ColumnSerializer.parent_row_initializer, delimiter=', ')
     row_init = self.foreach_col(
         ColumnSerializer.row_initializer, delimiter=',\n          ')
+    parent_separator = ',' if row_init else ''
     row_eq = self.foreach_col(ColumnSerializer.row_eq, delimiter=' &&\n       ')
     return f'''
   struct Row : public {self.parent_class_name}::Row {{
     Row({param},
         std::nullptr_t = nullptr)
-        : {self.parent_class_name}::Row({parent_row_init}),
+        : {self.parent_class_name}::Row({parent_row_init}){parent_separator}
           {row_init} {{
       type_ = "{self.table.sql_name}";
     }}
@@ -297,22 +337,28 @@
     '''
 
   def constructor(self) -> str:
-    col_init = self.foreach_col(
+    storage_init = self.foreach_col(
         ColumnSerializer.storage_init, delimiter=',\n        ')
     if self.table.parent:
       parent_param = f', {self.parent_class_name}* parent'
       parent_arg = 'parent'
-      parent_init = 'parent_(parent), '
+      parent_init = 'parent_(parent)' + (', ' if storage_init else '')
     else:
       parent_param = ''
       parent_arg = 'nullptr'
       parent_init = ''
+    col_init = self.foreach_col(ColumnSerializer.column_init)
+    if col_init:
+      olay = 'uint32_t olay_idx = static_cast<uint32_t>(overlays_.size()) - 1;'
+    else:
+      olay = ''
     return f'''
   explicit {self.table_name}(StringPool* pool{parent_param})
       : macros_internal::MacroTable(pool, {parent_arg}),
-        {parent_init}{col_init} {{
-    uint32_t overlay_idx = static_cast<uint32_t>(overlays_.size()) - 1;
-    {self.foreach_col(ColumnSerializer.column_init)}
+        {parent_init}{storage_init} {{
+    {self.foreach_col(ColumnSerializer.static_assert_flags)}
+    {olay}
+    {col_init}
   }}
     '''
 
@@ -383,6 +429,60 @@
   }};
       '''
 
+  def extend(self) -> str:
+    if not self.table.parent:
+      return ''
+    params = self.foreach_col(
+        ColumnSerializer.extend_parent_param, delimiter='\n, ')
+    args = self.foreach_col(
+        ColumnSerializer.extend_parent_param_arg, delimiter=', ')
+    delim = ',' if params else ''
+    return f'''
+  static std::unique_ptr<Table> ExtendParent(
+      const {self.parent_class_name}& parent{delim}
+      {params}) {{
+    return std::unique_ptr<Table>(new {self.table_name}(
+        parent.string_pool(), parent, RowMap(0, parent.row_count()){delim}
+        {args}));
+  }}
+
+  static std::unique_ptr<Table> SelectAndExtendParent(
+      const {self.parent_class_name}& parent,
+      std::vector<{self.parent_class_name}::RowNumber> parent_overlay{delim}
+      {params}) {{
+    std::vector<uint32_t> prs_untyped(parent_overlay.size());
+    for (uint32_t i = 0; i < parent_overlay.size(); ++i) {{
+      prs_untyped[i] = parent_overlay[i].row_number();
+    }}
+    return std::unique_ptr<Table>(new {self.table_name}(
+        parent.string_pool(), parent, RowMap(std::move(prs_untyped)){delim}
+        {args}));
+  }}
+    '''
+
+  def extend_constructor(self) -> str:
+    if not self.table.parent:
+      return ''
+    params = self.foreach_col(
+        ColumnSerializer.extend_parent_param, delimiter='\n, ')
+    if params:
+      olay = 'uint32_t olay_idx = static_cast<uint32_t>(overlays_.size()) - 1;'
+    else:
+      olay = ''
+    return f'''
+  {self.table_name}(StringPool* pool,
+            const {self.parent_class_name}& parent,
+            const RowMap& parent_overlay{',' if params else ''}
+            {params})
+      : macros_internal::MacroTable(pool, parent, parent_overlay) {{
+    {self.foreach_col(ColumnSerializer.static_assert_flags)}
+    {self.foreach_col(ColumnSerializer.extend_nullable_vector)}
+
+    {olay}
+    {self.foreach_col(ColumnSerializer.column_init)}
+  }}
+    '''
+
   def serialize(self) -> str:
     return f'''
 class {self.table_name} : public macros_internal::MacroTable {{
@@ -482,11 +582,14 @@
                      RowNumber(row_number)}};
   }}
 
+  {self.extend().strip()}
+
   {self.foreach_col(ColumnSerializer.accessor)}
 
   {self.foreach_col(ColumnSerializer.mutable_accessor)}
 
  private:
+  {self.extend_constructor().strip()}
   {self.parent_field().strip()}
   {self.foreach_col(ColumnSerializer.storage)}
 }};
@@ -502,7 +605,7 @@
 #ifndef {ifdef_guard}
 #define {ifdef_guard}
 
-#include "src/trace_processor/tables/macros.h"
+#include "src/trace_processor/tables/macros_internal.h"
 
 {include_paths_str}
 
diff --git a/python/generators/trace_processor_table/util.py b/python/generators/trace_processor_table/util.py
index 015f02c..1f0dc0c 100644
--- a/python/generators/trace_processor_table/util.py
+++ b/python/generators/trace_processor_table/util.py
@@ -14,7 +14,8 @@
 
 import dataclasses
 from dataclasses import dataclass
-import runpy
+import importlib
+import sys
 from typing import Dict
 from typing import List
 from typing import Set
@@ -92,71 +93,78 @@
 
   table: Table
   columns: List[ParsedColumn]
-  input_path: str
 
-  def parse_type(self, col_type: CppColumnType) -> ParsedType:
-    """Parses a CppColumnType into its constiuent parts."""
 
-    if isinstance(col_type, CppInt64):
-      return ParsedType('int64_t')
-    if isinstance(col_type, CppInt32):
-      return ParsedType('int32_t')
-    if isinstance(col_type, CppUint32):
-      return ParsedType('uint32_t')
-    if isinstance(col_type, CppDouble):
-      return ParsedType('double')
-    if isinstance(col_type, CppString):
-      return ParsedType('StringPool::Id')
+def parse_type_with_cols(table: Table, cols: List[Column],
+                         col_type: CppColumnType) -> ParsedType:
+  """Parses a CppColumnType into its constiuent parts."""
 
-    if isinstance(col_type, Alias):
-      col = next(c for c in self.columns
-                 if c.column.name == col_type.underlying_column)
-      return ParsedType(
-          self.parse_type(col.column.type).cpp_type,
-          is_alias=True,
-          alias_underlying_name=col.column.name)
+  if isinstance(col_type, CppInt64):
+    return ParsedType('int64_t')
+  if isinstance(col_type, CppInt32):
+    return ParsedType('int32_t')
+  if isinstance(col_type, CppUint32):
+    return ParsedType('uint32_t')
+  if isinstance(col_type, CppDouble):
+    return ParsedType('double')
+  if isinstance(col_type, CppString):
+    return ParsedType('StringPool::Id')
 
-    if isinstance(col_type, CppTableId):
-      return ParsedType(
-          f'{col_type.table.class_name}::Id', id_table=col_type.table)
+  if isinstance(col_type, Alias):
+    col = next(c for c in cols if c.name == col_type.underlying_column)
+    return ParsedType(
+        parse_type(table, col.type).cpp_type,
+        is_alias=True,
+        alias_underlying_name=col.name)
 
-    if isinstance(col_type, CppSelfTableId):
-      return ParsedType(
-          f'{self.table.class_name}::Id', is_self_id=True, id_table=self.table)
+  if isinstance(col_type, CppTableId):
+    return ParsedType(
+        f'{col_type.table.class_name}::Id', id_table=col_type.table)
 
-    if isinstance(col_type, CppOptional):
-      inner = self.parse_type(col_type.inner)
-      assert not inner.is_optional, 'Nested optional not allowed'
-      return dataclasses.replace(inner, is_optional=True)
+  if isinstance(col_type, CppSelfTableId):
+    return ParsedType(
+        f'{table.class_name}::Id', is_self_id=True, id_table=table)
 
-    raise Exception(f'Unknown type {col_type}')
+  if isinstance(col_type, CppOptional):
+    inner = parse_type(table, col_type.inner)
+    assert not inner.is_optional, 'Nested optional not allowed'
+    return dataclasses.replace(inner, is_optional=True)
 
-  def typed_column_type(self, col: ParsedColumn) -> str:
-    """Returns the TypedColumn/IdColumn C++ type for a given column."""
+  raise Exception(f'Unknown type {col_type}')
 
-    parsed = self.parse_type(col.column.type)
-    if col.is_implicit_id:
-      return f'IdColumn<{parsed.cpp_type}>'
-    return f'TypedColumn<{parsed.cpp_type_with_optionality()}>'
 
-  def find_table_deps(self) -> Set[str]:
-    """Finds all the other table class names this table depends on.
+def parse_type(table: Table, col_type: CppColumnType) -> ParsedType:
+  """Parses a CppColumnType into its constiuent parts."""
+  return parse_type_with_cols(table, table.columns, col_type)
 
-    By "depends", we mean this table in C++ would need the dependency to be
-    defined (or included) before this table is defined."""
 
-    deps: Set[str] = set()
-    if self.table.parent:
-      deps.add(self.table.parent.class_name)
-    for c in self.table.columns:
-      # Aliases cannot have dependencies so simply ignore them: trying to parse
-      # them before adding implicit columns can cause issues.
-      if isinstance(c.type, Alias):
-        continue
-      id_table = self.parse_type(c.type).id_table
-      if id_table:
-        deps.add(id_table.class_name)
-    return deps
+def typed_column_type(table: Table, col: ParsedColumn) -> str:
+  """Returns the TypedColumn/IdColumn C++ type for a given column."""
+
+  parsed = parse_type(table, col.column.type)
+  if col.is_implicit_id:
+    return f'IdColumn<{parsed.cpp_type}>'
+  return f'TypedColumn<{parsed.cpp_type_with_optionality()}>'
+
+
+def find_table_deps(table: Table) -> List[Table]:
+  """Finds all the other table class names this table depends on.
+
+  By "depends", we mean this table in C++ would need the dependency to be
+  defined (or included) before this table is defined."""
+
+  deps: Dict[str, Table] = {}
+  if table.parent:
+    deps[table.parent.class_name] = table.parent
+  for c in table.columns:
+    # Aliases cannot have dependencies so simply ignore them: trying to parse
+    # them before adding implicit columns can cause issues.
+    if isinstance(c.type, Alias):
+      continue
+    id_table = parse_type(table, c.type).id_table
+    if id_table:
+      deps[id_table.class_name] = id_table
+  return list(deps.values())
 
 
 def public_sql_name(table: Table) -> str:
@@ -165,10 +173,9 @@
   wrapping_view = table.wrapping_sql_view
   return wrapping_view.view_name if wrapping_view else table.sql_name
 
-def _create_implicit_columns_for_root(parsed: ParsedTable
-                                     ) -> List[ParsedColumn]:
+
+def _create_implicit_columns_for_root(table: Table) -> List[ParsedColumn]:
   """Given a root table, returns the implicit id and type columns."""
-  table = parsed.table
   assert table.parent is None
 
   sql_name = public_sql_name(table)
@@ -191,26 +198,25 @@
   ]
 
 
-def _topological_sort_tables(parsed: List[ParsedTable]) -> List[ParsedTable]:
+def _topological_sort_table_and_deps(parsed: List[Table]) -> List[Table]:
   """Topologically sorts a list of tables (i.e. dependenices appear earlier).
 
   See [1] for information on a topological sort. We do this to allow
   dependencies to be processed and appear ealier than their dependents.
 
   [1] https://en.wikipedia.org/wiki/Topological_sorting"""
-  table_to_parsed_table = {p.table.class_name: p for p in parsed}
   visited: Set[str] = set()
-  result: List[ParsedTable] = []
+  result: List[Table] = []
 
   # Topological sorting is really just a DFS where we put the nodes in the list
   # after any dependencies.
-  def dfs(t: ParsedTable):
-    if t.table.class_name in visited:
+  def dfs(t: Table):
+    if t.class_name in visited:
       return
-    visited.add(t.table.class_name)
+    visited.add(t.class_name)
 
-    for dep in t.find_table_deps():
-      dfs(table_to_parsed_table[dep])
+    for dep in find_table_deps(t):
+      dfs(dep)
     result.append(t)
 
   for p in parsed:
@@ -226,26 +232,27 @@
   return ColumnDoc(doc=doc)
 
 
-def parse_tables_from_files(input_paths: List[str]) -> List[ParsedTable]:
+def parse_tables_from_modules(modules: List[str]) -> List[ParsedTable]:
   """Creates a list of tables with the associated paths."""
 
   # Create a mapping from the table to a "parsed" version of the table.
+  tables: Dict[str, Table] = {}
+  for module in modules:
+    imported = importlib.import_module(module)
+    run_tables: List[Table] = imported.__dict__['ALL_TABLES']
+    for table in run_tables:
+      existing_table = tables.get(table.class_name)
+      assert not existing_table or existing_table == table
+      tables[table.class_name] = table
+
+  # Sort all the tables: note that this list may include tables which are not
+  # in |tables| dictionary due to dependencies on tables which live in a file
+  # not covered by |input_paths|.
+  sorted_tables = _topological_sort_table_and_deps(list(tables.values()))
+
   parsed_tables: Dict[str, ParsedTable] = {}
-  for in_path in input_paths:
-    tables: List[Table] = runpy.run_path(in_path)['ALL_TABLES']
-    for table in tables:
-      existing_table = parsed_tables.get(table.class_name)
-      assert not existing_table or existing_table.table == table
-      parsed_tables[table.class_name] = ParsedTable(table, [], in_path)
-
-  # Sort all the tables to be in order.
-  sorted_tables = _topological_sort_tables(list(parsed_tables.values()))
-
-  # Create the list of parsed columns
-  for i, parsed in enumerate(sorted_tables):
+  for table in sorted_tables:
     parsed_columns: List[ParsedColumn]
-    table = parsed.table
-
     if table.parent:
       parsed_parent = parsed_tables[table.parent.class_name]
       parsed_columns = [
@@ -253,13 +260,17 @@
           for c in parsed_parent.columns
       ]
     else:
-      parsed_columns = _create_implicit_columns_for_root(parsed)
+      parsed_columns = _create_implicit_columns_for_root(table)
 
     for c in table.columns:
       doc = table.tabledoc.columns.get(c.name) if table.tabledoc else None
       parsed_columns.append(ParsedColumn(c, _to_column_doc(doc)))
+    parsed_tables[table.class_name] = ParsedTable(table, parsed_columns)
 
-    sorted_tables[i] = dataclasses.replace(parsed, columns=parsed_columns)
-    parsed_tables[parsed.table.class_name] = sorted_tables[i]
-
-  return sorted_tables
+  # Only return tables which come directly from |input_paths|. This stops us
+  # generating tables which were not requested.
+  return [
+      parsed_tables[p.class_name]
+      for p in sorted_tables
+      if p.class_name in tables
+  ]
diff --git a/python/perfetto/prebuilts/manifests/trace_processor_shell.py b/python/perfetto/prebuilts/manifests/trace_processor_shell.py
index 85fb30e..2e9e826 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v33.1
+# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8583152,
+        8714576,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/trace_processor_shell',
     'sha256':
-        '35673d3546dec894b5d55147da2fad523a8f5917b42ec1c327c940b82d3ce565',
+        '9bdb89493f0f00db5d3a73166450ac2f6ee830de16415e79c5a0234990caa644',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7303384,
+        7286968,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/trace_processor_shell',
     'sha256':
-        'a4d301cf8c0c01d328a9253d5ba78f4249333d4b04236cf8be0c7dad2a65e7e0',
+        '948536035fbe680b47b94a99d320ff459450738e4aeeb16cef18364f0023622b',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8991600,
+        8576688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/trace_processor_shell',
     'sha256':
-        'e8dd82c1ec73fbbf4165ab0d9cbbb750cff5bcf723a1eab51adc9382bf652361',
+        '493698c81fffcabc340c72831b175962dba5a31dfe8572a6d5af083a116af4f8',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7117104,
+        6125384,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/trace_processor_shell',
     'sha256':
-        'bbfde44ec004815a36cecdc1dbc135f815f46ac6a3989c87cb0c577510c1c8fe',
+        '53f1e27603695cf92d22519993b6eafa9c60957d9cb33bd0b300df8573b87ebb',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8384816,
+        8036288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/trace_processor_shell',
     'sha256':
-        'c4f9a499e8c443961725448aabc04cd7dc18cb79883f6b8b615fd8f4ed7c8c16',
+        '2a2cda222c9d5e18b638057688babb00a3a975ccd4b7dd65f26211c2cb7767f9',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,55 +75,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        5823560,
+        5813384,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/trace_processor_shell',
     'sha256':
-        'ec5d23fc761021fe10a7cdb66d35590dab0216b2305f5163ace98da28b535fb8'
+        'f3ec4c194d0b06af5b296c1c479e6b29090e6b7cc7e58fbd55ca2919a126f0ee'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7474864,
+        7294768,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/trace_processor_shell',
     'sha256':
-        'f4877f51d0fbb8e9ead576e746a7adf7806b5cb2dffc4373a55ceeec21f615ff'
+        'f44f47d4b873ec68b6fa4f4c69a3e5a13d58b4d9cb2ec591fa687d4480c1950b'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8436764,
+        8090716,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/trace_processor_shell',
     'sha256':
-        '68ad60af32890f903afb7cbee7cc8f0f4f4b18dea7ab077cb1d807ea80053dcb'
+        '5636d8251747376787640bc3a4894ecf3091e4bf3d38b007003e1992fc5792df'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8781544,
+        8359784,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/trace_processor_shell',
     'sha256':
-        'edf5efca4cf46ffbd3586592490b14d61758198c7d46c1bc8e083b1ab19382f5'
+        '50440fa055ab998f6cf24f9a9a7388520cc854708735521505e10291bc52f3d0'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        8252928,
+        8130560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        '323a210f857ce840c4d69dfa7f9b0a32501ffa4a856e5667a0916e3f8006a5d0',
+        '5cbcf98e29a2d989523235e11e4e0dade692a295ebf47a6c93a09a050ce9bc91',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/python/perfetto/prebuilts/manifests/tracebox.py b/python/perfetto/prebuilts/manifests/tracebox.py
index effd4d1..46b064a 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v33.1
+# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1415776,
+        1432064,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/tracebox',
     'sha256':
-        '860cccef002f1a7216d301a09b97d7276b8a57c8d85ad1c3aa4697bb115ffca7',
+        '4ceb7646cd99303224ab5e7ff0a9f84c04f3c5466fff65a55dab65171ae9d482',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1309272,
+        1325704,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/tracebox',
     'sha256':
-        '9c079ac561064c33e9bdfe2e23e92fb95c025603e545c1aae31b2bd7de0398ad',
+        '2c560fcce5e19eb692e50487af134e2078347cdb79decba0c572917860528388',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2137040,
+        2155496,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/tracebox',
     'sha256':
-        '9eb9ce1a14432c284fecce7886786bb2555bcb6dfb4f00a2df2885984961a5fc',
+        '10b92180bb461a7e21be3f8b3d4640430a98d0547238ce095709213b378217d2',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1277896,
+        1288764,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/tracebox',
     'sha256':
-        '60c71b39be7e04d9d0278e36e7e4d33c32a03d6cc8a3782a9e5ed2484f3f2082',
+        'fa28950ce2b7a9345fbb9272f2dd04d3d4eb2a87f021df25e1e649840eae60b5',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2065704,
+        2082704,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/tracebox',
     'sha256':
-        'a8d9e9e186b5daf45ec80b975b9a3ad04cb578890beda136b821c80b9cc74995',
+        '85c371d79b8e23d22a293c29e6399dc311d891a6bd85d7eeaf2cb0179c69eb27',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,42 +75,42 @@
     'file_name':
         'tracebox',
     'file_size':
-        1161172,
+        1169364,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/tracebox',
     'sha256':
-        'f9dac5df26d471d1cf0aff942d7249da6b4122543e003813203ef128a15f93fd'
+        '40a3f31600f02dea10e290134d5c862e0e717f4f039756889a4e72c60f1591b6'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'tracebox',
     'file_size':
-        1764008,
+        1776296,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/tracebox',
     'sha256':
-        '3b325a09b6efae0939b73d4d74e6e01e3735508ed31b774f3a21765efab95099'
+        '562505fca18b34a97687dc002aeebcbf20acef68c8a8e48bed6d618c20e07c92'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'tracebox',
     'file_size':
-        1755052,
+        1767340,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/tracebox',
     'sha256':
-        '86e31fa7e2b476187a0222ac2cf6a4ee7e5f8fb5b0e019c1349d14534343a581'
+        'eb47eb43ba93403557dd15a61196799e945ec324d96109db2f155fb131f9996a'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'tracebox',
     'file_size':
-        2034344,
+        2054824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/tracebox',
     'sha256':
-        '7692f6ceaa5d2eb9da42486262610a1820a9b31d46255f624407bf712eff021d'
+        'a3ae6d108e041ba368a9770f952772f111865d4eff7c8e4e4e2f653f45017948'
 }]
diff --git a/python/perfetto/prebuilts/manifests/traceconv.py b/python/perfetto/prebuilts/manifests/traceconv.py
index fad02d9..b093ac6 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v33.1
+# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        7822272,
+        7904536,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/traceconv',
     'sha256':
-        '2b1bae4755ee0dd7f3a8e55653f8a7c344f688ea29700064ef8211c55bb4ae9f',
+        '037f84ac943f3f4d75447c668cc49c966fe3d85eca3a455c958b24fc6a9e314a',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -19,11 +19,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6604056,
+        6554600,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/traceconv',
     'sha256':
-        'c0bd6d1ebe2c61ffeefbd4f01426e9b853c81daf70530be7e78c97a4d3af100c',
+        'eda545ef4fa37fdfa1b47ced7cbbe0aa3c0df9bd161cacd7c78e6c55aef98d20',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -33,11 +33,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8122112,
+        7664384,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/traceconv',
     'sha256':
-        'c4c57d8e7b435822a1437b2dc7f7154f6ff2e197deff1f9284bbd36bbedb004f',
+        '24285e6e0e873d393fa5a993bac18ec8e1ab5fae6f4e3453214e095ef36e4c45',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -47,11 +47,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6692016,
+        5657944,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/traceconv',
     'sha256':
-        'da666eb9f80bcbec4c959f4adf493a59ff89e4106666fe1884291078dba0243b',
+        'c9af3d976f849fc75e96c2c552cb14fcc9eacce6fe7c45c4a8289080b0f66706',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -61,11 +61,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7575344,
+        7184224,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/traceconv',
     'sha256':
-        '8ca00c39c5ec7bd78576f64c4ab05e663d803b06b36fbddf968825edbe236fca',
+        'c6dc936492d58a40cd8e0b58abc46bd479e0c1c387cd1ba29198a6c9b2000d7a',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -75,55 +75,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        5376396,
+        5325260,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/traceconv',
     'sha256':
-        'b77e7f0274ba45ff32d34df347845bc996763291fcc6b2a697f56c0c9a543150'
+        '963267dcb58cdde9f61a952e5cb7f3557833209d3251e7fdcefc3b52db54f77b'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        6793744,
+        6572688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/traceconv',
     'sha256':
-        'e83f3d43f8782cb57e9d3e8a3cd31826c9713da9f92bd8d8be2c48872ed423eb'
+        '87373c351fe5e947826cd957438cab8a37a352bf83b1cbbb15fe276eee9d873a'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        7694692,
+        7303588,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/traceconv',
     'sha256':
-        'c9ee2c3c91d6c68cb7f52a626767bde5e267f34c6ddf987ff73eec3d813c0a2c'
+        'dfc4e714963b5ed662d29d6028ffa69e67f8cd2f9a28223f715437a260fd456f'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        7940680,
+        7482056,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/traceconv',
     'sha256':
-        'f75122ca3e6bbe393b705c3bc5514d81c57f38bf408d857d89c4268b79a39e08'
+        '79c666c629fcffd810635270b45e58b40ed253d22650f41550057e5d8f8c49a7'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        7239168,
+        7072768,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/windows-amd64/traceconv.exe',
     'sha256':
-        '5be5d698f69d44b4baa8ae1f21955becad9d0e6774e967f923b8386744002cfe',
+        '40fac80fdeae443a924e160650c94629e6463c1fb5a4f04f4ef6e9e5e72a3965',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index 9aa5fe5..f390a02 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/metrics.descriptor.sha1 b/python/perfetto/trace_processor/metrics.descriptor.sha1
deleted file mode 100644
index 0a69bdf..0000000
--- a/python/perfetto/trace_processor/metrics.descriptor.sha1
+++ /dev/null
@@ -1,6 +0,0 @@
-
-// SHA1(tools/gen_binary_descriptors)
-// 6886b319e65925c037179e71a803b8473d06dc7d
-// SHA1(protos/perfetto/metrics/metrics.proto)
-// b07fa0b4ad2deed79d2d8cc320f70502e19eee0c
-  
\ No newline at end of file
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor.sha1 b/python/perfetto/trace_processor/trace_processor.descriptor.sha1
deleted file mode 100644
index 7f7827f..0000000
--- a/python/perfetto/trace_processor/trace_processor.descriptor.sha1
+++ /dev/null
@@ -1,6 +0,0 @@
-
-// SHA1(tools/gen_binary_descriptors)
-// 6886b319e65925c037179e71a803b8473d06dc7d
-// SHA1(protos/perfetto/trace_processor/trace_processor.proto)
-// 59f86a32aa28f29e290d8ce3f97461725aa8b9f8
-  
\ No newline at end of file
diff --git a/src/android_stats/perfetto_atoms.h b/src/android_stats/perfetto_atoms.h
index e9ef08e..122dfad 100644
--- a/src/android_stats/perfetto_atoms.h
+++ b/src/android_stats/perfetto_atoms.h
@@ -47,6 +47,7 @@
   // they log the trigger name.
   kTracedTriggerStartTracing = 41,
   kTracedTriggerStopTracing = 42,
+  kTracedTriggerCloneSnapshot = 53,
 
   // Guardrails inside traced.
   kTracedEnableTracingExistingTraceSession = 18,
diff --git a/src/base/http/BUILD.gn b/src/base/http/BUILD.gn
index e086134..dda19a7 100644
--- a/src/base/http/BUILD.gn
+++ b/src/base/http/BUILD.gn
@@ -40,8 +40,11 @@
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
   ]
-  sources = [
-    "http_server_unittest.cc",
-    "sha1_unittest.cc",
-  ]
+  sources = [ "sha1_unittest.cc" ]
+
+  # The HTTP server unittests cannot be run in parallel. Chromium runs tests
+  # in parallel on some bots so exclude all of these ones.
+  if (!build_with_chromium) {
+    sources += [ "http_server_unittest.cc" ]
+  }
 }
diff --git a/src/base/metatrace.cc b/src/base/metatrace.cc
index 1464ed3..51b8b83 100644
--- a/src/base/metatrace.cc
+++ b/src/base/metatrace.cc
@@ -36,13 +36,6 @@
 std::atomic<bool> RingBuffer::has_overruns_;
 Record RingBuffer::bankruptcy_record_;
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-constexpr size_t RingBuffer::kCapacity;
-constexpr uint16_t Record::kTypeMask;
-constexpr uint16_t Record::kTypeCounter;
-constexpr uint16_t Record::kTypeEvent;
-#endif
-
 namespace {
 
 // std::function<> is not trivially de/constructible. This struct wraps it in a
diff --git a/src/cloud_trace_processor/BUILD.gn b/src/cloud_trace_processor/BUILD.gn
new file mode 100644
index 0000000..e9a4bfc
--- /dev/null
+++ b/src/cloud_trace_processor/BUILD.gn
@@ -0,0 +1,64 @@
+# 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.gni")
+import("../../gn/test.gni")
+
+assert(
+    enable_perfetto_trace_processor && enable_perfetto_trace_processor_sqlite)
+
+# The "core" business logic of cloud trace processor which is agnostic to the
+# RPC transport. Allows wrapping with any RPC framework capable of handling
+# protobufs.
+static_library("cloud_trace_processor") {
+  complete_static_lib = true
+  deps = [ ":sources" ]
+  public_deps = [ "../../include/perfetto/ext/cloud_trace_processor" ]
+}
+
+source_set("sources") {
+  sources = [
+    "orchestrator_impl.cc",
+    "orchestrator_impl.h",
+    "trace_processor_wrapper.cc",
+    "trace_processor_wrapper.h",
+    "worker_impl.cc",
+    "worker_impl.h",
+  ]
+  deps = [
+    "../../gn:default_deps",
+    "../../include/perfetto/ext/cloud_trace_processor",
+    "../../protos/perfetto/cloud_trace_processor:lite",
+    "../base",
+    "../base/threading",
+    "../protozero",
+    "../protozero:proto_ring_buffer",
+    "../trace_processor:lib",
+    "../trace_processor/rpc",
+    "../trace_processor/util",
+  ]
+}
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  sources = [ "trace_processor_wrapper_unittest.cc" ]
+  deps = [
+    ":sources",
+    "../../gn:default_deps",
+    "../../gn:gtest_and_gmock",
+    "../../protos/perfetto/cloud_trace_processor:lite",
+    "../base",
+    "../base/threading",
+  ]
+}
diff --git a/src/cloud_trace_processor/orchestrator_impl.cc b/src/cloud_trace_processor/orchestrator_impl.cc
new file mode 100644
index 0000000..69b831f
--- /dev/null
+++ b/src/cloud_trace_processor/orchestrator_impl.cc
@@ -0,0 +1,210 @@
+/*
+ * 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.
+ */
+
+#include "src/cloud_trace_processor/orchestrator_impl.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/threading/future.h"
+#include "perfetto/ext/base/threading/stream.h"
+#include "perfetto/ext/cloud_trace_processor/worker.h"
+#include "protos/perfetto/cloud_trace_processor/common.pb.h"
+#include "protos/perfetto/cloud_trace_processor/orchestrator.pb.h"
+#include "protos/perfetto/cloud_trace_processor/worker.pb.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto {
+namespace cloud_trace_processor {
+namespace {
+
+base::Future<base::Status> CreateResponseToStatus(
+    base::StatusOr<protos::TracePoolShardCreateResponse> response_or) {
+  return response_or.status();
+}
+
+base::Future<base::Status> SetTracesResponseToStatus(
+    base::StatusOr<protos::TracePoolShardSetTracesResponse> response_or) {
+  return response_or.status();
+}
+
+base::Future<base::StatusOr<protos::TracePoolQueryResponse>>
+RpcResponseToPoolResponse(
+    base::StatusOr<protos::TracePoolShardQueryResponse> resp) {
+  RETURN_IF_ERROR(resp.status());
+  protos::TracePoolQueryResponse ret;
+  ret.set_trace(std::move(resp->trace()));
+  *ret.mutable_result() = std::move(*resp->mutable_result());
+  return ret;
+}
+
+base::StatusOrStream<protos::TracePoolShardSetTracesResponse>
+RoundRobinSetTraces(const std::vector<std::unique_ptr<Worker>>& workers,
+                    const std::vector<std::string>& traces) {
+  uint32_t worker_idx = 0;
+  std::vector<protos::TracePoolShardSetTracesArgs> protos;
+  protos.resize(workers.size());
+  for (const auto& trace : traces) {
+    protos[worker_idx].add_traces(trace);
+    worker_idx = (worker_idx + 1) % workers.size();
+  }
+
+  using ShardResponse = protos::TracePoolShardSetTracesResponse;
+  std::vector<base::StatusOrStream<ShardResponse>> streams;
+  for (uint32_t i = 0; i < protos.size(); ++i) {
+    streams.emplace_back(workers[i]->TracePoolShardSetTraces(protos[i]));
+  }
+  return base::FlattenStreams(std::move(streams));
+}
+}  // namespace
+
+Orchestrator::~Orchestrator() = default;
+
+std::unique_ptr<Orchestrator> Orchestrator::CreateInProcess(
+    std::vector<std::unique_ptr<Worker>> workers) {
+  return std::unique_ptr<Orchestrator>(
+      new OrchestratorImpl(std::move(workers)));
+}
+
+OrchestratorImpl::OrchestratorImpl(std::vector<std::unique_ptr<Worker>> workers)
+    : workers_(std::move(workers)) {}
+
+base::StatusOrFuture<protos::TracePoolCreateResponse>
+OrchestratorImpl::TracePoolCreate(const protos::TracePoolCreateArgs& args) {
+  if (args.pool_type() != protos::TracePoolType::SHARED) {
+    return base::StatusOr<protos::TracePoolCreateResponse>(
+        base::ErrStatus("Currently only SHARED pools are supported"));
+  }
+  if (!args.has_shared_pool_name()) {
+    return base::StatusOr<protos::TracePoolCreateResponse>(
+        base::ErrStatus("Pool name must be provided for SHARED pools"));
+  }
+
+  std::string id = "shared:" + args.shared_pool_name();
+  TracePool* exist = pools_.Find(id);
+  if (exist) {
+    return base::StatusOr<protos::TracePoolCreateResponse>(
+        base::ErrStatus("Pool %s already exists", id.c_str()));
+  }
+  protos::TracePoolShardCreateArgs group_args;
+  group_args.set_pool_id(id);
+  group_args.set_pool_type(args.pool_type());
+
+  using ShardResponse = protos::TracePoolShardCreateResponse;
+  std::vector<base::StatusOrStream<ShardResponse>> shards;
+  for (uint32_t i = 0; i < workers_.size(); ++i) {
+    shards.emplace_back(
+        base::StreamFromFuture(workers_[i]->TracePoolShardCreate(group_args)));
+  }
+  return base::FlattenStreams(std::move(shards))
+      .MapFuture(&CreateResponseToStatus)
+      .Collect(base::AllOkCollector())
+      .ContinueWith(
+          [this, id](base::StatusOr<ShardResponse> resp)
+              -> base::StatusOrFuture<protos::TracePoolCreateResponse> {
+            RETURN_IF_ERROR(resp.status());
+            auto it_and_inserted = pools_.Insert(id, TracePool());
+            if (!it_and_inserted.second) {
+              return base::ErrStatus("Unable to insert pool %s", id.c_str());
+            }
+            return protos::TracePoolCreateResponse();
+          });
+}
+
+base::StatusOrFuture<protos::TracePoolSetTracesResponse>
+OrchestratorImpl::TracePoolSetTraces(
+    const protos::TracePoolSetTracesArgs& args) {
+  std::string id = args.pool_id();
+  TracePool* pool = pools_.Find(id);
+  if (!pool) {
+    return base::StatusOr<protos::TracePoolSetTracesResponse>(
+        base::ErrStatus("Unable to find pool %s", id.c_str()));
+  }
+  if (!pool->loaded_traces.empty()) {
+    return base::StatusOr<protos::TracePoolSetTracesResponse>(base::ErrStatus(
+        "Incrementally adding/removing items to pool not currently supported"));
+  }
+  pool->loaded_traces.assign(args.traces().begin(), args.traces().end());
+  return RoundRobinSetTraces(workers_, pool->loaded_traces)
+      .MapFuture(&SetTracesResponseToStatus)
+      .Collect(base::AllOkCollector())
+      .ContinueWith(
+          [](base::Status status)
+              -> base::StatusOrFuture<protos::TracePoolSetTracesResponse> {
+            RETURN_IF_ERROR(status);
+            return protos::TracePoolSetTracesResponse();
+          });
+}
+
+base::StatusOrStream<protos::TracePoolQueryResponse>
+OrchestratorImpl::TracePoolQuery(const protos::TracePoolQueryArgs& args) {
+  TracePool* pool = pools_.Find(args.pool_id());
+  if (!pool) {
+    return base::StreamOf(base::StatusOr<protos::TracePoolQueryResponse>(
+        base::ErrStatus("Unable to find pool %s", args.pool_id().c_str())));
+  }
+  protos::TracePoolShardQueryArgs shard_args;
+  *shard_args.mutable_pool_id() = args.pool_id();
+  *shard_args.mutable_sql_query() = args.sql_query();
+
+  using ShardResponse = protos::TracePoolShardQueryResponse;
+  std::vector<base::StatusOrStream<ShardResponse>> streams;
+  for (uint32_t i = 0; i < workers_.size(); ++i) {
+    streams.emplace_back(workers_[i]->TracePoolShardQuery(shard_args));
+  }
+  return base::FlattenStreams(std::move(streams))
+      .MapFuture(&RpcResponseToPoolResponse);
+}
+
+base::StatusOrFuture<protos::TracePoolDestroyResponse>
+OrchestratorImpl::TracePoolDestroy(const protos::TracePoolDestroyArgs& args) {
+  std::string id = args.pool_id();
+  TracePool* pool = pools_.Find(id);
+  if (!pool) {
+    return base::StatusOr<protos::TracePoolDestroyResponse>(
+        base::ErrStatus("Unable to find pool %s", id.c_str()));
+  }
+  protos::TracePoolShardDestroyArgs shard_args;
+  *shard_args.mutable_pool_id() = id;
+
+  using ShardResponse = protos::TracePoolShardDestroyResponse;
+  std::vector<base::StatusOrStream<ShardResponse>> streams;
+  for (uint32_t i = 0; i < workers_.size(); ++i) {
+    streams.emplace_back(
+        base::StreamFromFuture(workers_[i]->TracePoolShardDestroy(shard_args)));
+  }
+  return base::FlattenStreams(std::move(streams))
+      .MapFuture(
+          [](base::StatusOr<ShardResponse> resp) -> base::Future<base::Status> {
+            return resp.status();
+          })
+      .Collect(base::AllOkCollector())
+      .ContinueWith(
+          [this, id](base::Status status)
+              -> base::StatusOrFuture<protos::TracePoolDestroyResponse> {
+            RETURN_IF_ERROR(status);
+            PERFETTO_CHECK(pools_.Erase(id));
+            return protos::TracePoolDestroyResponse();
+          });
+}
+
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
diff --git a/src/cloud_trace_processor/orchestrator_impl.h b/src/cloud_trace_processor/orchestrator_impl.h
new file mode 100644
index 0000000..eef55e5
--- /dev/null
+++ b/src/cloud_trace_processor/orchestrator_impl.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_CLOUD_TRACE_PROCESSOR_ORCHESTRATOR_IMPL_H_
+#define SRC_CLOUD_TRACE_PROCESSOR_ORCHESTRATOR_IMPL_H_
+
+#include <memory>
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/threading/future.h"
+#include "perfetto/ext/cloud_trace_processor/orchestrator.h"
+
+namespace perfetto {
+namespace protos {
+class TracePoolShardCreateArgs;
+}
+
+namespace cloud_trace_processor {
+
+class OrchestratorImpl : public Orchestrator {
+ public:
+  explicit OrchestratorImpl(std::vector<std::unique_ptr<Worker>> workers);
+
+  base::StatusOrStream<protos::TracePoolQueryResponse> TracePoolQuery(
+      const protos::TracePoolQueryArgs&) override;
+
+  base::StatusOrFuture<protos::TracePoolCreateResponse> TracePoolCreate(
+      const protos::TracePoolCreateArgs&) override;
+
+  base::StatusOrFuture<protos::TracePoolSetTracesResponse> TracePoolSetTraces(
+      const protos::TracePoolSetTracesArgs&) override;
+
+  base::StatusOrFuture<protos::TracePoolDestroyResponse> TracePoolDestroy(
+      const protos::TracePoolDestroyArgs&) override;
+
+ private:
+  struct TracePool {
+    std::vector<std::string> loaded_traces;
+  };
+  std::vector<std::unique_ptr<Worker>> workers_;
+  base::FlatHashMap<std::string, TracePool> pools_;
+};
+
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_CLOUD_TRACE_PROCESSOR_ORCHESTRATOR_IMPL_H_
diff --git a/src/cloud_trace_processor/trace_processor_wrapper.cc b/src/cloud_trace_processor/trace_processor_wrapper.cc
new file mode 100644
index 0000000..5093e52
--- /dev/null
+++ b/src/cloud_trace_processor/trace_processor_wrapper.cc
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+#include "src/cloud_trace_processor/trace_processor_wrapper.h"
+
+#include <atomic>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/file_utils.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/threading/future.h"
+#include "perfetto/ext/base/threading/poll.h"
+#include "perfetto/ext/base/threading/stream.h"
+#include "perfetto/ext/base/threading/thread_pool.h"
+#include "perfetto/ext/base/threading/util.h"
+#include "perfetto/protozero/proto_utils.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "perfetto/trace_processor/trace_processor.h"
+#include "protos/perfetto/cloud_trace_processor/worker.pb.h"
+#include "src/protozero/proto_ring_buffer.h"
+#include "src/trace_processor/rpc/query_result_serializer.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto {
+namespace cloud_trace_processor {
+namespace {
+
+using trace_processor::QueryResultSerializer;
+using trace_processor::TraceBlob;
+using trace_processor::TraceBlobView;
+using trace_processor::TraceProcessor;
+using Statefulness = TraceProcessorWrapper::Statefulness;
+
+struct QueryRunner {
+  QueryRunner(std::shared_ptr<TraceProcessor> _tp,
+              std::string _query,
+              std::string _trace_path,
+              Statefulness _statefulness)
+      : tp(std::move(_tp)),
+        query(std::move(_query)),
+        trace_path(std::move(_trace_path)),
+        statefulness(_statefulness) {}
+
+  std::optional<protos::TracePoolShardQueryResponse> operator()() {
+    if (!has_more) {
+      if (statefulness == Statefulness::kStateless) {
+        tp->RestoreInitialTables();
+      }
+      return std::nullopt;
+    }
+    // If the serializer does not exist yet, that means we have not yet run
+    // the query so make sure to do that first.
+    EnsureSerializerExists();
+    has_more = serializer->Serialize(&result);
+
+    protos::TracePoolShardQueryResponse resp;
+    *resp.mutable_trace() = trace_path;
+    resp.mutable_result()->ParseFromArray(result.data(),
+                                          static_cast<int>(result.size()));
+    result.clear();
+    return std::make_optional(std::move(resp));
+  }
+
+  void EnsureSerializerExists() {
+    if (serializer) {
+      return;
+    }
+    auto it = tp->ExecuteQuery(query);
+    serializer.reset(new QueryResultSerializer(std::move(it)));
+  }
+
+  std::shared_ptr<TraceProcessor> tp;
+  std::string query;
+  std::string trace_path;
+  TraceProcessorWrapper::Statefulness statefulness;
+
+  // shared_ptr to allow copying when this type is coerced to std::function.
+  std::shared_ptr<QueryResultSerializer> serializer;
+  std::vector<uint8_t> result;
+  bool has_more = true;
+};
+
+}  // namespace
+
+TraceProcessorWrapper::TraceProcessorWrapper(std::string trace_path,
+                                             base::ThreadPool* thread_pool,
+                                             Statefulness statefulness)
+    : trace_path_(std::move(trace_path)),
+      thread_pool_(thread_pool),
+      statefulness_(statefulness) {
+  trace_processor::Config config;
+  config.ingest_ftrace_in_raw_table = false;
+  trace_processor_ = TraceProcessor::CreateInstance(config);
+}
+
+base::StatusFuture TraceProcessorWrapper::LoadTrace(
+    base::StatusOrStream<std::vector<uint8_t>> file_stream) {
+  if (trace_processor_.use_count() != 1) {
+    return base::ErrStatus("Request is already in flight");
+  }
+  return std::move(file_stream)
+      .MapFuture(
+          [this](base::StatusOr<std::vector<uint8_t>> d) -> base::StatusFuture {
+            RETURN_IF_ERROR(d.status());
+            return base::RunOnceOnThreadPool<base::Status>(
+                thread_pool_, [res = std::move(*d), tp = trace_processor_] {
+                  return tp->Parse(TraceBlobView(
+                      TraceBlob::CopyFrom(res.data(), res.size())));
+                });
+          })
+      .Collect(base::AllOkCollector())
+      .ContinueWith([this](base::Status status) -> base::StatusFuture {
+        RETURN_IF_ERROR(status);
+        return base::RunOnceOnThreadPool<base::Status>(
+            thread_pool_, [tp = trace_processor_] {
+              tp->NotifyEndOfFile();
+              return base::OkStatus();
+            });
+      });
+}
+
+base::StatusOrStream<protos::TracePoolShardQueryResponse>
+TraceProcessorWrapper::Query(const std::string& query) {
+  using StatusOrResponse = base::StatusOr<protos::TracePoolShardQueryResponse>;
+  if (trace_processor_.use_count() != 1) {
+    return base::StreamOf<StatusOrResponse>(
+        base::ErrStatus("Request is already in flight"));
+  }
+  return base::RunOnThreadPool<StatusOrResponse>(
+      thread_pool_,
+      QueryRunner(trace_processor_, query, trace_path_, statefulness_));
+}
+
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
diff --git a/src/cloud_trace_processor/trace_processor_wrapper.h b/src/cloud_trace_processor/trace_processor_wrapper.h
new file mode 100644
index 0000000..9916b72
--- /dev/null
+++ b/src/cloud_trace_processor/trace_processor_wrapper.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_CLOUD_TRACE_PROCESSOR_TRACE_PROCESSOR_WRAPPER_H_
+#define SRC_CLOUD_TRACE_PROCESSOR_TRACE_PROCESSOR_WRAPPER_H_
+
+#include "perfetto/ext/base/threading/future.h"
+#include "perfetto/ext/base/threading/stream.h"
+#include "perfetto/ext/base/threading/thread_pool.h"
+#include "perfetto/trace_processor/trace_processor.h"
+#include "src/trace_processor/rpc/query_result_serializer.h"
+
+namespace perfetto {
+namespace protos {
+
+class TracePoolShardQueryResponse;
+
+}  // namespace protos
+}  // namespace perfetto
+
+namespace perfetto {
+namespace cloud_trace_processor {
+
+// Wrapper class around an instance of TraceProcessor to adapt it for the needs
+// of a CloudTraceProcessor Worker.
+class TraceProcessorWrapper {
+ public:
+  enum Statefulness {
+    // Indicates that the state of the trace processor instance should be purged
+    // after every query.
+    kStateless,
+
+    // Indicates that the state of the trace processor instance should be
+    // preserved across queries.
+    kStateful,
+  };
+
+  TraceProcessorWrapper(std::string trace_path,
+                        base::ThreadPool*,
+                        Statefulness);
+
+  // Loads the trace given a stream of chunks to parse.
+  base::StatusFuture LoadTrace(
+      base::StatusOrStream<std::vector<uint8_t>> file_stream);
+
+  // Executes the given query on the trace processor and returns the results
+  // as a stream.
+  base::StatusOrStream<protos::TracePoolShardQueryResponse> Query(
+      const std::string& sql);
+
+ private:
+  using TraceProcessor = trace_processor::TraceProcessor;
+
+  TraceProcessorWrapper(const TraceProcessorWrapper&) = delete;
+  TraceProcessorWrapper& operator=(const TraceProcessorWrapper&) = delete;
+
+  TraceProcessorWrapper(TraceProcessorWrapper&&) = delete;
+  TraceProcessorWrapper& operator=(TraceProcessorWrapper&&) = delete;
+
+  const std::string trace_path_;
+  base::ThreadPool* thread_pool_ = nullptr;
+  const Statefulness statefulness_ = Statefulness::kStateless;
+  std::shared_ptr<TraceProcessor> trace_processor_;
+};
+
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_CLOUD_TRACE_PROCESSOR_TRACE_PROCESSOR_WRAPPER_H_
diff --git a/src/cloud_trace_processor/trace_processor_wrapper_unittest.cc b/src/cloud_trace_processor/trace_processor_wrapper_unittest.cc
new file mode 100644
index 0000000..8739783
--- /dev/null
+++ b/src/cloud_trace_processor/trace_processor_wrapper_unittest.cc
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "src/cloud_trace_processor/trace_processor_wrapper.h"
+#include <cstdint>
+#include <vector>
+
+#include "perfetto/base/flat_set.h"
+#include "perfetto/base/platform_handle.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/threading/stream.h"
+#include "perfetto/ext/base/threading/thread_pool.h"
+#include "perfetto/ext/base/threading/util.h"
+#include "protos/perfetto/cloud_trace_processor/worker.pb.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace cloud_trace_processor {
+namespace {
+
+using SF = TraceProcessorWrapper::Statefulness;
+
+const char kSimpleSystrace[] = R"--(# tracer
+  surfaceflinger-598   (  598) [004] .... 10852.771242: tracing_mark_write: B|598|some event
+  surfaceflinger-598   (  598) [004] .... 10852.771245: tracing_mark_write: E|598
+)--";
+
+base::StatusOr<std::vector<uint8_t>> SimpleSystrace() {
+  return std::vector<uint8_t>(kSimpleSystrace,
+                              kSimpleSystrace + strlen(kSimpleSystrace));
+}
+
+std::vector<base::StatusOr<std::vector<uint8_t>>> SimpleSystraceChunked() {
+  std::string systrace(kSimpleSystrace);
+  std::vector<base::StatusOr<std::vector<uint8_t>>> chunks;
+  for (auto& chunk : base::SplitString(systrace, "\n")) {
+    auto with_newline = chunk + "\n";
+    chunks.push_back(std::vector<uint8_t>(
+        with_newline.data(), with_newline.data() + with_newline.size()));
+  }
+
+  return chunks;
+}
+
+template <typename T>
+T WaitForFutureReady(base::Future<T>& future) {
+  base::FlatSet<base::PlatformHandle> ready;
+  base::FlatSet<base::PlatformHandle> interested;
+  base::PollContext ctx(&interested, &ready);
+  auto res = future.Poll(&ctx);
+  for (; res.IsPending(); res = future.Poll(&ctx)) {
+    PERFETTO_CHECK(interested.size() == 1);
+    base::BlockUntilReadableFd(*interested.begin());
+    interested = {};
+  }
+  return res.item();
+}
+
+template <typename T>
+std::optional<T> WaitForStreamReady(base::Stream<T>& stream) {
+  base::FlatSet<base::PlatformHandle> ready;
+  base::FlatSet<base::PlatformHandle> interested;
+  base::PollContext ctx(&interested, &ready);
+  auto res = stream.PollNext(&ctx);
+  for (; res.IsPending(); res = stream.PollNext(&ctx)) {
+    PERFETTO_CHECK(interested.size() == 1);
+    base::BlockUntilReadableFd(*interested.begin());
+    interested = {};
+  }
+  return res.IsDone() ? std::nullopt : std::make_optional(res.item());
+}
+
+TEST(TraceProcessorWrapperUnittest, Stateful) {
+  base::ThreadPool pool(1);
+  TraceProcessorWrapper wrapper("foobar", &pool, SF::kStateful);
+  {
+    auto load = wrapper.LoadTrace(base::StreamOf(SimpleSystrace()));
+    base::Status status = WaitForFutureReady(load);
+    ASSERT_TRUE(status.ok()) << status.message();
+  }
+  {
+    auto stream = wrapper.Query("CREATE VIEW foo AS SELECT ts, dur FROM slice");
+    auto proto = WaitForStreamReady(stream);
+    ASSERT_TRUE(proto.has_value());
+    ASSERT_TRUE(proto->ok()) << proto->status().message();
+
+    ASSERT_FALSE(WaitForStreamReady(stream).has_value());
+  }
+  {
+    auto stream = wrapper.Query("SELECT ts, dur FROM foo");
+    auto proto = WaitForStreamReady(stream);
+
+    ASSERT_TRUE(proto.has_value());
+    ASSERT_TRUE(proto->ok()) << proto->status().message();
+
+    ASSERT_EQ(proto->value().trace(), "foobar");
+
+    auto& result = proto.value()->result();
+    ASSERT_EQ(result.batch_size(), 1);
+    ASSERT_EQ(result.batch(0).cells_size(), 2);
+
+    ASSERT_EQ(result.batch(0).cells(0),
+              protos::QueryResult::CellsBatch::CELL_VARINT);
+    ASSERT_EQ(result.batch(0).cells(1),
+              protos::QueryResult::CellsBatch::CELL_VARINT);
+    ASSERT_EQ(result.batch(0).varint_cells(0), 10852771242000);
+    ASSERT_EQ(result.batch(0).varint_cells(1), 3000);
+
+    ASSERT_FALSE(WaitForStreamReady(stream).has_value());
+  }
+}
+
+TEST(TraceProcessorWrapperUnittest, Stateless) {
+  base::ThreadPool pool(1);
+  TraceProcessorWrapper wrapper("foobar", &pool, SF::kStateless);
+  {
+    auto load = wrapper.LoadTrace(base::StreamOf(SimpleSystrace()));
+    base::Status status = WaitForFutureReady(load);
+    ASSERT_TRUE(status.ok()) << status.message();
+  }
+  {
+    auto stream = wrapper.Query("CREATE VIEW foo AS SELECT ts, dur FROM slice");
+    auto proto = WaitForStreamReady(stream);
+    ASSERT_TRUE(proto.has_value());
+    ASSERT_TRUE(proto->ok()) << proto->status().message();
+
+    ASSERT_FALSE(WaitForStreamReady(stream).has_value());
+  }
+
+  // Second CREATE VIEW should also succeed because the first one should have
+  // been wiped.
+  {
+    auto stream = wrapper.Query("CREATE VIEW foo AS SELECT ts, dur FROM slice");
+    auto proto = WaitForStreamReady(stream);
+    ASSERT_TRUE(proto.has_value());
+    ASSERT_TRUE(proto->ok()) << proto->status().message();
+
+    ASSERT_FALSE(WaitForStreamReady(stream).has_value());
+  }
+
+  // Selecting from it should return an error.
+  {
+    auto stream = wrapper.Query("SELECT ts, dur FROM foo");
+    auto proto = WaitForStreamReady(stream);
+    ASSERT_TRUE(proto.has_value());
+    ASSERT_TRUE(proto->ok()) << proto->status().message();
+    ASSERT_TRUE(proto->value().result().has_error());
+
+    ASSERT_FALSE(WaitForStreamReady(stream).has_value());
+  }
+}
+
+TEST(TraceProcessorWrapperUnittest, Chunked) {
+  base::ThreadPool pool(1);
+  TraceProcessorWrapper wrapper("foobar", &pool, SF::kStateless);
+  {
+    auto chunked = SimpleSystraceChunked();
+    ASSERT_EQ(chunked.size(), 3u);
+    auto load = wrapper.LoadTrace(base::StreamFrom(chunked));
+    base::Status status = WaitForFutureReady(load);
+    ASSERT_TRUE(status.ok()) << status.message();
+  }
+  {
+    auto stream = wrapper.Query("SELECT ts, dur FROM slice");
+    auto proto = WaitForStreamReady(stream);
+
+    ASSERT_TRUE(proto.has_value());
+    ASSERT_TRUE(proto->ok()) << proto->status().message();
+
+    ASSERT_EQ(proto->value().trace(), "foobar");
+
+    auto& result = proto.value()->result();
+    ASSERT_EQ(result.batch_size(), 1);
+    ASSERT_EQ(result.batch(0).cells_size(), 2);
+
+    ASSERT_EQ(result.batch(0).cells(0),
+              protos::QueryResult::CellsBatch::CELL_VARINT);
+    ASSERT_EQ(result.batch(0).cells(1),
+              protos::QueryResult::CellsBatch::CELL_VARINT);
+    ASSERT_EQ(result.batch(0).varint_cells(0), 10852771242000);
+    ASSERT_EQ(result.batch(0).varint_cells(1), 3000);
+
+    ASSERT_FALSE(WaitForStreamReady(stream).has_value());
+  }
+}
+
+}  // namespace
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
diff --git a/src/cloud_trace_processor/worker_impl.cc b/src/cloud_trace_processor/worker_impl.cc
new file mode 100644
index 0000000..6f11560
--- /dev/null
+++ b/src/cloud_trace_processor/worker_impl.cc
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+#include "src/cloud_trace_processor/worker_impl.h"
+
+#include <memory>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/threading/stream.h"
+#include "perfetto/ext/base/uuid.h"
+#include "protos/perfetto/cloud_trace_processor/common.pb.h"
+#include "protos/perfetto/cloud_trace_processor/orchestrator.pb.h"
+#include "protos/perfetto/cloud_trace_processor/worker.pb.h"
+#include "src/cloud_trace_processor/trace_processor_wrapper.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto {
+namespace cloud_trace_processor {
+
+Worker::~Worker() = default;
+
+std::unique_ptr<Worker> Worker::CreateInProcesss(CtpEnvironment* environment,
+                                                 base::ThreadPool* pool) {
+  return std::make_unique<WorkerImpl>(environment, pool);
+}
+
+WorkerImpl::WorkerImpl(CtpEnvironment* environment, base::ThreadPool* pool)
+    : environment_(environment), thread_pool_(pool) {}
+
+base::StatusOrFuture<protos::TracePoolShardCreateResponse>
+WorkerImpl::TracePoolShardCreate(const protos::TracePoolShardCreateArgs& args) {
+  if (args.pool_type() == protos::TracePoolType::DEDICATED) {
+    return base::ErrStatus("Dedicated pools are not currently supported");
+  }
+  auto it_and_inserted = shards_.Insert(args.pool_id(), TracePoolShard());
+  if (!it_and_inserted.second) {
+    return base::ErrStatus("Shard for pool %s already exists",
+                           args.pool_id().c_str());
+  }
+  return base::StatusOr(protos::TracePoolShardCreateResponse());
+}
+
+base::StatusOrStream<protos::TracePoolShardSetTracesResponse>
+WorkerImpl::TracePoolShardSetTraces(
+    const protos::TracePoolShardSetTracesArgs& args) {
+  using Response = protos::TracePoolShardSetTracesResponse;
+  using StatusOrResponse = base::StatusOr<Response>;
+
+  TracePoolShard* shard = shards_.Find(args.pool_id());
+  if (!shard) {
+    return base::StreamOf<StatusOrResponse>(base::ErrStatus(
+        "Unable to find shard for pool %s", args.pool_id().c_str()));
+  }
+
+  std::vector<base::StatusOrStream<Response>> streams;
+  for (const std::string& trace : args.traces()) {
+    // TODO(lalitm): add support for stateful trace processor in dedicated
+    // pools.
+    auto tp = std::make_unique<TraceProcessorWrapper>(
+        trace, thread_pool_, TraceProcessorWrapper::Statefulness::kStateless);
+    auto load_trace_future =
+        tp->LoadTrace(environment_->ReadFile(trace))
+            .ContinueWith(
+                [trace](base::Status status) -> base::Future<StatusOrResponse> {
+                  RETURN_IF_ERROR(status);
+                  protos::TracePoolShardSetTracesResponse resp;
+                  *resp.mutable_trace() = trace;
+                  return resp;
+                });
+    streams.emplace_back(base::StreamFromFuture(std::move(load_trace_future)));
+    shard->tps.emplace_back(std::move(tp));
+  }
+  return base::FlattenStreams(std::move(streams));
+}
+
+base::StatusOrStream<protos::TracePoolShardQueryResponse>
+WorkerImpl::TracePoolShardQuery(const protos::TracePoolShardQueryArgs& args) {
+  using Response = protos::TracePoolShardQueryResponse;
+  using StatusOrResponse = base::StatusOr<Response>;
+  TracePoolShard* shard = shards_.Find(args.pool_id());
+  if (!shard) {
+    return base::StreamOf<StatusOrResponse>(base::ErrStatus(
+        "Unable to find shard for pool %s", args.pool_id().c_str()));
+  }
+  std::vector<base::StatusOrStream<Response>> streams;
+  streams.reserve(shard->tps.size());
+  for (std::unique_ptr<TraceProcessorWrapper>& tp : shard->tps) {
+    streams.emplace_back(tp->Query(args.sql_query()));
+  }
+  return base::FlattenStreams(std::move(streams));
+}
+
+base::StatusOrFuture<protos::TracePoolShardDestroyResponse>
+WorkerImpl::TracePoolShardDestroy(
+    const protos::TracePoolShardDestroyArgs& args) {
+  if (!shards_.Erase(args.pool_id())) {
+    return base::ErrStatus("Unable to find shard for pool %s",
+                           args.pool_id().c_str());
+  }
+  return base::StatusOr(protos::TracePoolShardDestroyResponse());
+}
+
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
diff --git a/src/cloud_trace_processor/worker_impl.h b/src/cloud_trace_processor/worker_impl.h
new file mode 100644
index 0000000..c7dc754
--- /dev/null
+++ b/src/cloud_trace_processor/worker_impl.h
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#ifndef SRC_CLOUD_TRACE_PROCESSOR_WORKER_IMPL_H_
+#define SRC_CLOUD_TRACE_PROCESSOR_WORKER_IMPL_H_
+
+#include <vector>
+
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/threading/thread_pool.h"
+#include "perfetto/ext/cloud_trace_processor/environment.h"
+#include "perfetto/ext/cloud_trace_processor/worker.h"
+#include "src/cloud_trace_processor/trace_processor_wrapper.h"
+
+namespace perfetto {
+namespace protos {
+
+enum GroupType : int;
+
+}  // namespace protos
+}  // namespace perfetto
+
+namespace perfetto {
+namespace cloud_trace_processor {
+
+class WorkerImpl : public Worker {
+ public:
+  explicit WorkerImpl(CtpEnvironment*, base::ThreadPool*);
+
+  base::StatusOrFuture<protos::TracePoolShardCreateResponse>
+  TracePoolShardCreate(const protos::TracePoolShardCreateArgs&) override;
+
+  base::StatusOrStream<protos::TracePoolShardSetTracesResponse>
+  TracePoolShardSetTraces(const protos::TracePoolShardSetTracesArgs&) override;
+
+  base::StatusOrStream<protos::TracePoolShardQueryResponse> TracePoolShardQuery(
+      const protos::TracePoolShardQueryArgs&) override;
+
+  base::StatusOrFuture<protos::TracePoolShardDestroyResponse>
+  TracePoolShardDestroy(const protos::TracePoolShardDestroyArgs&) override;
+
+ private:
+  struct TracePoolShard {
+    std::vector<std::unique_ptr<TraceProcessorWrapper>> tps;
+  };
+  CtpEnvironment* const environment_;
+  base::ThreadPool* const thread_pool_;
+  base::FlatHashMap<std::string, TracePoolShard> shards_;
+};
+
+}  // namespace cloud_trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_CLOUD_TRACE_PROCESSOR_WORKER_IMPL_H_
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index 4a2b081..3052341 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -19,6 +19,7 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/proc_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/string_splitter.h"
 
 #include <fcntl.h>
 #include <stdio.h>
@@ -201,15 +202,17 @@
 const char* kStateDir = "/data/misc/perfetto-traces";
 
 PerfettoCmd::PerfettoCmd() {
-  PERFETTO_DCHECK(!g_perfetto_cmd);
-  g_perfetto_cmd = this;
+  // Only the main thread instance on the main thread will receive ctrl-c.
+  if (!g_perfetto_cmd)
+    g_perfetto_cmd = this;
 }
 
 PerfettoCmd::~PerfettoCmd() {
-  PERFETTO_DCHECK(g_perfetto_cmd == this);
-  g_perfetto_cmd = nullptr;
-  if (ctrl_c_handler_installed_) {
-    task_runner_.RemoveFileDescriptorWatch(ctrl_c_evt_.fd());
+  if (g_perfetto_cmd == this) {
+    g_perfetto_cmd = nullptr;
+    if (ctrl_c_handler_installed_) {
+      task_runner_.RemoveFileDescriptorWatch(ctrl_c_evt_.fd());
+    }
   }
 }
 
@@ -228,6 +231,9 @@
                              session, identified by its ID (see --query).
   --config         -c      : /path/to/trace/config/file or - for stdin
   --out            -o      : /path/to/out/trace/file or - for stdout
+                             If using CLONE_SNAPSHOT triggers, each snapshot
+                             will be saved in a new file with a counter suffix
+                             (e.g., file.0, file.1, file.2).
   --txt                    : Parse config as pbtxt. Not for production use.
                              Not a stable API.
   --query                  : Queries the service state and prints it as
@@ -339,6 +345,7 @@
     return 1;
   }
 
+  optind = 1;  // Reset getopt state. It's reused by the snapshot thread.
   for (;;) {
     int option =
         getopt_long(argc, argv, "hc:o:dDt:b:s:a:", long_options, nullptr);
@@ -365,6 +372,14 @@
         opts.categories.emplace_back("power/gpu_frequency");
         PERFETTO_CHECK(CreateConfigFromOptions(opts, &test_config));
         trace_config_raw = test_config.SerializeAsString();
+      } else if (strcmp(optarg, ":mem") == 0) {
+        // This is used by OnCloneSnapshotTriggerReceived(), which passes the
+        // original trace config as a member field. This is needed because, in
+        // the new PerfettoCmd instance, we need to know upfront trace config
+        // fields that affect the behaviour of perfetto_cmd, e.g., the guardrail
+        // overrides, the unique_session_name, the reporter API package etc.
+        PERFETTO_CHECK(!snapshot_config_.empty());
+        trace_config_raw = snapshot_config_;
       } else {
         if (!base::ReadFile(optarg, &trace_config_raw)) {
           PERFETTO_PLOG("Could not open %s", optarg);
@@ -573,7 +588,7 @@
 
   bool parsed = false;
   const bool will_trace_or_trigger = !is_attach() && !query_service_;
-  if (!will_trace_or_trigger || clone_tsid_) {
+  if (!will_trace_or_trigger) {
     if ((!trace_config_raw.empty() || has_config_options)) {
       PERFETTO_ELOG("Cannot specify a trace config with this option");
       return 1;
@@ -587,7 +602,7 @@
     }
     parsed = CreateConfigFromOptions(config_options, trace_config_.get());
   } else {
-    if (trace_config_raw.empty()) {
+    if (trace_config_raw.empty() && !clone_tsid_) {
       PERFETTO_ELOG("The TraceConfig is empty");
       return 1;
     }
@@ -784,8 +799,12 @@
       packet_writer_ = CreateFilePacketWriter(trace_out_stream_.get());
   }
 
-  if (trace_config_->compression_type() ==
-      TraceConfig::COMPRESSION_TYPE_DEFLATE) {
+  // TODO(b/281043457): this code path will go away after Android U. Compression
+  // has been moved to the service. This code is here only as a fallback in case
+  // of bugs in the U timeframe.
+  if (trace_config_->compress_from_cli() &&
+      trace_config_->compression_type() ==
+          TraceConfig::COMPRESSION_TYPE_DEFLATE) {
     if (packet_writer_) {
 #if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
       packet_writer_ = CreateZipPacketWriter(std::move(packet_writer_));
@@ -819,6 +838,7 @@
 #endif
     }
 
+    PERFETTO_CHECK(!snapshot_thread_);  // No threads before demonization.
     base::Daemonize([this]() -> int {
       background_wait_pipe_.wr.reset();
 
@@ -992,9 +1012,16 @@
   connected_ = true;
   LogUploadEvent(PerfettoStatsdAtom::kOnConnect);
 
+  uint32_t events_mask = 0;
+  if (GetTriggerMode(*trace_config_) ==
+      TraceConfig::TriggerConfig::CLONE_SNAPSHOT) {
+    events_mask |= ObservableEvents::TYPE_CLONE_TRIGGER_HIT;
+  }
   if (background_wait_) {
-    consumer_endpoint_->ObserveEvents(
-        perfetto::ObservableEvents::TYPE_ALL_DATA_SOURCES_STARTED);
+    events_mask |= ObservableEvents::TYPE_ALL_DATA_SOURCES_STARTED;
+  }
+  if (events_mask) {
+    consumer_endpoint_->ObserveEvents(events_mask);
   }
 
   if (query_service_) {
@@ -1119,6 +1146,14 @@
     // be marked as "E" in the event log. Hence why LOG and not ELOG here.
     PERFETTO_LOG("Service error: %s", error.c_str());
 
+    // In case of errors don't leave a partial file around. This happens
+    // frequently in the case of --save-for-bugreport if there is no eligible
+    // trace. See also b/279753347 .
+    if (bytes_written_ == 0 && !trace_out_path_.empty() &&
+        trace_out_path_ != "-") {
+      remove(trace_out_path_.c_str());
+    }
+
     // Update guardrail state even if we failed. This is for two
     // reasons:
     // 1. Keeps compatibility with pre-stats code which used to
@@ -1214,6 +1249,9 @@
 }
 
 void PerfettoCmd::SetupCtrlCSignalHandler() {
+  // Only the main thread instance should handle CTRL+C.
+  if (g_perfetto_cmd != this)
+    return;
   ctrl_c_handler_installed_ = true;
   base::InstallCtrlCHandler([] {
     if (!g_perfetto_cmd)
@@ -1282,17 +1320,18 @@
   // TODO(eseckler): Support GetTraceStats().
 }
 
-void PerfettoCmd::OnSessionCloned(bool success, const std::string& error) {
+void PerfettoCmd::OnSessionCloned(const OnSessionClonedArgs& args) {
   PERFETTO_DLOG("Cloned tracing session %" PRIu64 ", success=%d",
-                clone_tsid_.value_or(0), success);
+                clone_tsid_.value_or(0), args.success);
   std::string full_error;
-  if (!success) {
+  if (!args.success) {
     full_error = "Failed to clone tracing session " +
-                 std::to_string(clone_tsid_.value_or(0)) + ": " + error;
+                 std::to_string(clone_tsid_.value_or(0)) + ": " + args.error;
   }
 
   // Kick off the readback and file finalization (as if we started tracing and
   // reached the duration_ms timeout).
+  uuid_ = args.uuid.ToString();
   ReadbackTraceDataAndQuit(full_error);
 }
 
@@ -1413,6 +1452,69 @@
   if (observable_events.all_data_sources_started()) {
     NotifyBgProcessPipe(kBackgroundOk);
   }
+  if (observable_events.has_clone_trigger_hit()) {
+    int64_t tsid = observable_events.clone_trigger_hit().tracing_session_id();
+    OnCloneSnapshotTriggerReceived(static_cast<TracingSessionID>(tsid));
+  }
+}
+
+void PerfettoCmd::OnCloneSnapshotTriggerReceived(TracingSessionID tsid) {
+  PERFETTO_DLOG("Creating snapshot for tracing session %" PRIu64, tsid);
+
+  // Only the main thread instance should be handling snapshots.
+  // We should never end up in a state where each secondary PerfettoCmd
+  // instance handles other snapshots and creates other threads.
+  PERFETTO_CHECK(g_perfetto_cmd == this);
+
+  std::string cmdline;
+  auto add_argv = [&cmdline](const std::string& str) {
+    cmdline.append(str);
+    cmdline.append("\0", 1);
+  };
+  add_argv("perfetto");
+  add_argv("--config");
+  add_argv(":mem");  // Use the copied config from `snapshot_config_`.
+  add_argv("--clone");
+  add_argv(std::to_string(tsid));
+  if (upload_flag_) {
+    add_argv("--upload");
+  } else if (!trace_out_path_.empty()) {
+    add_argv("--out");
+    add_argv(trace_out_path_ + "." + std::to_string(snapshot_count_++));
+  } else {
+    PERFETTO_FATAL("Cannot use CLONE_SNAPSHOT with the current cmdline args");
+  }
+
+  if (!snapshot_thread_) {
+    // The destructor of the main-thread's PerfettoCmdMain will destroy and
+    // join the secondary thread that we are crating here.
+    snapshot_thread_.reset(new base::ThreadTaskRunner(
+        base::ThreadTaskRunner::CreateAndStart("snapshot")));
+  }
+
+  // We need to pass a copy of the trace config to the new PerfettoCmd instance
+  // because the trace config defines a bunch of properties that are used by the
+  // cmdline client (reporter API package, guardrails, etc).
+  std::string trace_config_copy = trace_config_->SerializeAsString();
+
+  snapshot_thread_->PostTask([tsid, cmdline, trace_config_copy] {
+    int argc = 0;
+    char* argv[32];
+    // `splitter` needs to live on the stack for the whole scope as it owns the
+    // underlying string storage (that gets std::moved) passed PerfettoCmd.
+    base::StringSplitter splitter(std::move(cmdline), '\0');
+    while (splitter.Next()) {
+      argv[argc++] = splitter.cur_token();
+      PERFETTO_CHECK(static_cast<size_t>(argc) < base::ArraySize(argv));
+    }
+    perfetto::PerfettoCmd cmd;
+    cmd.snapshot_config_ = std::move(trace_config_copy);
+    auto cmdline_res = cmd.ParseCmdlineAndMaybeDaemonize(argc, argv);
+    PERFETTO_CHECK(!cmdline_res.has_value());  // No daemonization expected.
+    int res = cmd.ConnectToServiceRunAndMaybeNotify();
+    if (res)
+      PERFETTO_ELOG("Cloning session %" PRIu64 " failed (%d)", tsid, res);
+  });
 }
 
 void PerfettoCmd::LogUploadEvent(PerfettoStatsdAtom atom) {
diff --git a/src/perfetto_cmd/perfetto_cmd.h b/src/perfetto_cmd/perfetto_cmd.h
index 9504ca5..b55cbc8 100644
--- a/src/perfetto_cmd/perfetto_cmd.h
+++ b/src/perfetto_cmd/perfetto_cmd.h
@@ -28,6 +28,7 @@
 #include "perfetto/ext/base/event_fd.h"
 #include "perfetto/ext/base/pipe.h"
 #include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/thread_task_runner.h"
 #include "perfetto/ext/base/unix_task_runner.h"
 #include "perfetto/ext/base/weak_ptr.h"
 #include "perfetto/ext/tracing/core/consumer.h"
@@ -67,7 +68,7 @@
   void OnAttach(bool, const TraceConfig&) override;
   void OnTraceStats(bool, const TraceStats&) override;
   void OnObservableEvents(const ObservableEvents&) override;
-  void OnSessionCloned(bool, const std::string&) override;
+  void OnSessionCloned(const OnSessionClonedArgs&) override;
 
   void SignalCtrlC() { ctrl_c_evt_.Notify(); }
 
@@ -116,6 +117,8 @@
   // will have no effect.
   void NotifyBgProcessPipe(BgProcessStatus status);
 
+  void OnCloneSnapshotTriggerReceived(TracingSessionID);
+
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
   static base::ScopedFile CreateUnlinkedTmpFile();
   void SaveTraceIntoIncidentOrCrash();
@@ -162,6 +165,14 @@
   // How long we expect to trace for or 0 if the trace is indefinite.
   uint32_t expected_duration_ms_ = 0;
   bool trace_data_timeout_armed_ = false;
+
+  // The aux thread that is used to invoke secondary instances of PerfettoCmd
+  // to create snapshots. This is used only when the trace config involves a
+  // CLONE_SNAPSHOT trigger.
+  std::unique_ptr<base::ThreadTaskRunner> snapshot_thread_;
+  int snapshot_count_ = 0;
+  std::string snapshot_config_;
+
   base::WeakPtrFactory<PerfettoCmd> weak_factory_{this};
 };
 
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
index 0fb7be3..fec5b6d 100644
--- a/src/profiling/perf/perf_producer.cc
+++ b/src/profiling/perf/perf_producer.cc
@@ -16,8 +16,10 @@
 
 #include "src/profiling/perf/perf_producer.h"
 
+#include <optional>
 #include <random>
 #include <utility>
+#include <vector>
 
 #include <unistd.h>
 
@@ -26,7 +28,9 @@
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/metatrace.h"
+#include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/base/weak_ptr.h"
 #include "perfetto/ext/tracing/core/basic_types.h"
@@ -80,6 +84,35 @@
   return static_cast<size_t>(sysconf(_SC_NPROCESSORS_CONF));
 }
 
+std::vector<uint32_t> GetOnlineCpus() {
+  size_t cpu_count = NumberOfCpus();
+  if (cpu_count == 0) {
+    return {};
+  }
+
+  static constexpr char kOnlineValue[] = "1\n";
+  std::vector<uint32_t> online_cpus;
+  online_cpus.reserve(cpu_count);
+  for (uint32_t cpu = 0; cpu < cpu_count; ++cpu) {
+    std::string res;
+    base::StackString<1024> path("/sys/devices/system/cpu/cpu%u/online", cpu);
+    if (!base::ReadFile(path.c_str(), &res)) {
+      // Always consider CPU 0 to be online if the "online" file does not exist
+      // for it. There seem to be several assumptions in the kernel which make
+      // CPU 0 special so this is a pretty safe bet.
+      if (cpu != 0) {
+        return {};
+      }
+      res = kOnlineValue;
+    }
+    if (res != kOnlineValue) {
+      continue;
+    }
+    online_cpus.push_back(cpu);
+  }
+  return online_cpus;
+}
+
 int32_t ToBuiltinClock(int32_t clockid) {
   switch (clockid) {
     case CLOCK_REALTIME:
@@ -394,9 +427,14 @@
     return;
   }
 
-  size_t num_cpus = NumberOfCpus();
+  std::vector<uint32_t> online_cpus = GetOnlineCpus();
+  if (online_cpus.empty()) {
+    PERFETTO_ELOG("No online CPUs found.");
+    return;
+  }
+
   std::vector<EventReader> per_cpu_readers;
-  for (uint32_t cpu = 0; cpu < num_cpus; cpu++) {
+  for (uint32_t cpu : online_cpus) {
     std::optional<EventReader> event_reader =
         EventReader::ConfigureEvents(cpu, event_config.value());
     if (!event_reader.has_value()) {
diff --git a/src/protozero/filtering/filter_util.cc b/src/protozero/filtering/filter_util.cc
index 8e81cb0..d17a36b 100644
--- a/src/protozero/filtering/filter_util.cc
+++ b/src/protozero/filtering/filter_util.cc
@@ -67,9 +67,13 @@
 FilterUtil::FilterUtil() = default;
 FilterUtil::~FilterUtil() = default;
 
-bool FilterUtil::LoadMessageDefinition(const std::string& proto_file,
-                                       const std::string& root_message,
-                                       const std::string& proto_dir_path) {
+bool FilterUtil::LoadMessageDefinition(
+    const std::string& proto_file,
+    const std::string& root_message,
+    const std::string& proto_dir_path,
+    const std::set<std::string>& passthrough_fields) {
+  passthrough_fields_ = passthrough_fields;
+  passthrough_fields_seen_.clear();
   // The protobuf compiler doesn't like backslashes and prints an error like:
   // Error C:\it7mjanpw3\perfetto-a16500 -1:0: Backslashes, consecutive slashes,
   // ".", or ".." are not allowed in the virtual path.
@@ -122,6 +126,22 @@
   // future without realizing) when performing the Dedupe() pass.
   DescriptorsByNameMap descriptors_by_full_name;
   ParseProtoDescriptor(root_msg, &descriptors_by_full_name);
+
+  // If the user specified a set of fields to pass through, print an error and
+  // fail if any of the passed fields have not been seen while recursing in the
+  // schema. This is to avoid typos or naming changes to be silently ignored.
+  std::vector<std::string> unused_passthrough;
+  std::set_difference(passthrough_fields_.begin(), passthrough_fields_.end(),
+                      passthrough_fields_seen_.begin(),
+                      passthrough_fields_seen_.end(),
+                      std::back_inserter(unused_passthrough));
+  for (const std::string& message_and_field : unused_passthrough) {
+    PERFETTO_ELOG("Field not found %s", message_and_field.c_str());
+  }
+  if (!unused_passthrough.empty()) {
+    PERFETTO_ELOG("Syntax: perfetto.protos.MessageName:field_name");
+    return false;
+  }
   return true;
 }
 
@@ -145,7 +165,15 @@
     auto& field = msg->fields[field_id];
     field.name = proto_field->name();
     field.type = proto_field->type_name();
-    if (proto_field->message_type()) {
+
+    std::string message_and_field = msg->full_name + ":" + field.name;
+    bool passthrough = false;
+    if (passthrough_fields_.count(message_and_field)) {
+      field.type = "bytes";
+      passthrough = true;
+      passthrough_fields_seen_.insert(message_and_field);
+    }
+    if (proto_field->message_type() && !passthrough) {
       msg->has_nested_fields = true;
       // Recurse.
       field.nested_type = ParseProtoDescriptor(proto_field->message_type(),
@@ -253,8 +281,11 @@
       }
 
       const Message* nested_type = id_and_field.second.nested_type;
+      bool passthrough = false;
       if (nested_type) {
-        PERFETTO_CHECK(!result.simple_field() || !filter_bytecode);
+        // result.simple_field might be true if the generated bytecode is
+        // passing through a whole submessage without recursing.
+        passthrough = result.simple_field();
         if (seen_msgs.find(nested_type) == seen_msgs.end()) {
           seen_msgs.insert(nested_type);
           queue.emplace_back(result.nested_msg_index, nested_type);
@@ -267,6 +298,8 @@
       std::string stripped_nested =
           nested_type ? " " + StripPrefix(nested_type->full_name, root_prefix)
                       : "";
+      if (passthrough)
+        stripped_nested += "  # PASSTHROUGH";
       fprintf(print_stream_, "%-60s %3u %-8s %-32s%s\n", stripped_name.c_str(),
               field_id, field.type.c_str(), field.name.c_str(),
               stripped_nested.c_str());
diff --git a/src/protozero/filtering/filter_util.h b/src/protozero/filtering/filter_util.h
index 0c5e74f..276ec6d 100644
--- a/src/protozero/filtering/filter_util.h
+++ b/src/protozero/filtering/filter_util.h
@@ -22,6 +22,7 @@
 #include <list>
 #include <map>
 #include <optional>
+#include <set>
 #include <string>
 
 // We include this intentionally instead of forward declaring to allow
@@ -46,9 +47,14 @@
   // root_message: fully qualified message name (e.g., perfetto.protos.Trace).
   //     If empty, the first message in the file will be used.
   // proto_dir_path: the root for .proto includes. If empty uses CWD.
-  bool LoadMessageDefinition(const std::string& proto_file,
-                             const std::string& root_message,
-                             const std::string& proto_dir_path);
+  // passthrough: an optional set of fields that should be transparently passed
+  //     through without recursing further.
+  //     Syntax: "perfetto.protos.TracePacket:trace_config"
+  bool LoadMessageDefinition(
+      const std::string& proto_file,
+      const std::string& root_message,
+      const std::string& proto_dir_path,
+      const std::set<std::string>& passthrough_fields = {});
 
   // Deduplicates leaf messages having the same sets of field ids.
   // It changes the internal state and affects the behavior of next calls to
@@ -103,6 +109,12 @@
 
   // list<> because pointers need to be stable.
   std::list<Message> descriptors_;
+  std::set<std::string> passthrough_fields_;
+
+  // Used only for debugging aid, to print out an error message when the user
+  // specifies a field to pass through but it doesn't exist.
+  std::set<std::string> passthrough_fields_seen_;
+
   FILE* print_stream_ = stdout;
 };
 
diff --git a/src/protozero/filtering/filter_util_unittest.cc b/src/protozero/filtering/filter_util_unittest.cc
index 7ad4d6f..2b13ae6 100644
--- a/src/protozero/filtering/filter_util_unittest.cc
+++ b/src/protozero/filtering/filter_util_unittest.cc
@@ -302,5 +302,42 @@
             FilterToText(filter, bytecode));
 }
 
+TEST(SchemaParserTest, Passthrough) {
+  auto schema = MkTemp(R"(
+  syntax = "proto2";
+  message Root {
+    optional int32 i32 = 13;
+    optional TracePacket packet = 7;
+  }
+  message TraceConfig {
+    optional int32 f3 = 3;
+    optional int64 f4 = 4;
+  }
+  message TracePacket {
+    optional int32 f1 = 3;
+    optional int64 f2 = 4;
+    optional TraceConfig cfg = 5;
+  }
+  )");
+
+  FilterUtil filter;
+  std::set<std::string> passthrough{"TracePacket:cfg"};
+  ASSERT_TRUE(
+      filter.LoadMessageDefinition(schema.path(), "Root", "", passthrough));
+
+  EXPECT_EQ(R"(Root 7 message packet TracePacket
+Root 13 int32 i32
+TracePacket 3 int32 f1
+TracePacket 4 int64 f2
+TracePacket 5 bytes cfg
+)",
+            FilterToText(filter));
+
+  std::string bytecode = filter.GenerateFilterBytecode();
+  // If we generate bytecode from the schema itself, all fields are allowed and
+  // the result is identical to the unfiltered output.
+  EXPECT_EQ(FilterToText(filter), FilterToText(filter, bytecode));
+}
+
 }  // namespace
 }  // namespace protozero
diff --git a/src/protozero/filtering/message_filter_unittest.cc b/src/protozero/filtering/message_filter_unittest.cc
index 83cb911..3159bfc 100644
--- a/src/protozero/filtering/message_filter_unittest.cc
+++ b/src/protozero/filtering/message_filter_unittest.cc
@@ -128,6 +128,87 @@
   }
 }
 
+TEST(MessageFilterTest, Passthrough) {
+  auto schema = perfetto::base::TempFile::Create();
+  static const char kSchema[] = R"(
+  syntax = "proto2";
+  message TracePacket {
+    optional int64 timestamp = 1;
+    optional TraceConfig cfg = 2;
+    optional TraceConfig cfg_filtered = 3;
+    optional string other = 4;
+  };
+  message SubConfig {
+    optional string f4 = 6;
+  }
+  message TraceConfig {
+    optional int64 f1 = 3;
+    optional string f2 = 4;
+    optional SubConfig f3 = 5;
+  }
+  )";
+
+  perfetto::base::WriteAll(*schema, kSchema, strlen(kSchema));
+  perfetto::base::FlushFile(*schema);
+
+  FilterUtil filter;
+  ASSERT_TRUE(filter.LoadMessageDefinition(
+      schema.path(), "", "", {"TracePacket:other", "TracePacket:cfg"}));
+  std::string bytecode = filter.GenerateFilterBytecode();
+  ASSERT_GT(bytecode.size(), 0u);
+
+  HeapBuffered<Message> msg;
+  msg->AppendVarInt(/*field_id=*/1, 10);
+  msg->AppendString(/*field_id=*/4, "other_string");
+
+  // Fill `cfg`.
+  auto* nest = msg->BeginNestedMessage<Message>(/*field_id=*/2);
+  nest->AppendVarInt(/*field_id=*/3, 100);
+  nest->AppendString(/*field_id=*/4, "f2.payload");
+  nest->AppendString(/*field_id=*/99, "not_in_original_schema");
+  auto* nest2 = nest->BeginNestedMessage<Message>(/*field_id=*/5);
+  nest2->AppendString(/*field_id=*/6, "subconfig.f4");
+  nest2->Finalize();
+  nest->Finalize();
+
+  // Fill `cfg_filtered`.
+  nest = msg->BeginNestedMessage<Message>(/*field_id=*/3);
+  nest->AppendVarInt(/*field_id=*/3, 200);  // This should be propagated.
+  nest->AppendVarInt(/*field_id=*/6, 300);  // This shoudl be filtered out.
+  nest->Finalize();
+
+  MessageFilter flt;
+  ASSERT_TRUE(flt.LoadFilterBytecode(bytecode.data(), bytecode.size()));
+
+  std::vector<uint8_t> encoded = msg.SerializeAsArray();
+
+  auto filtered = flt.FilterMessage(encoded.data(), encoded.size());
+  ASSERT_LT(filtered.size, encoded.size());
+
+  ProtoDecoder dec(filtered.data.get(), filtered.size);
+  EXPECT_EQ(dec.FindField(1).as_int64(), 10);
+  EXPECT_EQ(dec.FindField(4).as_std_string(), "other_string");
+
+  EXPECT_TRUE(dec.FindField(2).valid());
+  ProtoDecoder nest_dec(dec.FindField(2).as_bytes());
+  EXPECT_EQ(nest_dec.FindField(3).as_int32(), 100);
+  EXPECT_EQ(nest_dec.FindField(4).as_std_string(), "f2.payload");
+  EXPECT_TRUE(nest_dec.FindField(5).valid());
+  ProtoDecoder nest_dec2(nest_dec.FindField(5).as_bytes());
+  EXPECT_EQ(nest_dec2.FindField(6).as_std_string(), "subconfig.f4");
+
+  // Field 99 should be preserved anyways even if it wasn't in the original
+  // schema because the whole TracePacket submessage was passed through.
+  EXPECT_TRUE(nest_dec.FindField(99).valid());
+  EXPECT_EQ(nest_dec.FindField(99).as_std_string(), "not_in_original_schema");
+
+  // Check that the field `cfg_filtered` contains only `f1`,`f2`,`f3`.
+  EXPECT_TRUE(dec.FindField(3).valid());
+  ProtoDecoder nest_dec3(dec.FindField(3).as_bytes());
+  EXPECT_EQ(nest_dec3.FindField(3).as_int32(), 200);
+  EXPECT_FALSE(nest_dec3.FindField(6).valid());
+}
+
 TEST(MessageFilterTest, ChangeRoot) {
   auto schema = perfetto::base::TempFile::Create();
   static const char kSchema[] = R"(
diff --git a/src/protozero/packed_repeated_fields.cc b/src/protozero/packed_repeated_fields.cc
index 16d4539..a884b73 100644
--- a/src/protozero/packed_repeated_fields.cc
+++ b/src/protozero/packed_repeated_fields.cc
@@ -20,11 +20,6 @@
 
 namespace protozero {
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-// static
-constexpr size_t PackedBufferBase::kOnStackStorageSize;
-#endif
-
 void PackedBufferBase::GrowSlowpath() {
   size_t write_off = static_cast<size_t>(write_ptr_ - storage_begin_);
   size_t old_size = static_cast<size_t>(storage_end_ - storage_begin_);
diff --git a/src/protozero/proto_decoder_unittest.cc b/src/protozero/proto_decoder_unittest.cc
index 0991f88..1e962d7 100644
--- a/src/protozero/proto_decoder_unittest.cc
+++ b/src/protozero/proto_decoder_unittest.cc
@@ -592,5 +592,26 @@
   ASSERT_FALSE(field.valid());
 }
 
+// Check what happens when trying to parse packed repeated field and finding a
+// mismatching wire type instead. A compliant protobuf decoder should accept it,
+// but protozero doesn't handle that. At least it shouldn't crash.
+TEST(ProtoDecoderTest, PacketRepeatedWireTypeMismatch) {
+  protozero::HeapBuffered<pbtest::PackedRepeatedFields> message;
+  // A proper packed encoding should have a length delimited wire type. Use a
+  // var int wire type instead.
+  constexpr int kFieldId = pbtest::PackedRepeatedFields::kFieldInt32FieldNumber;
+  message->AppendTinyVarInt(kFieldId, 5);
+  auto data = message.SerializeAsArray();
+
+  pbtest::PackedRepeatedFields::Decoder decoder(data.data(), data.size());
+  bool parse_error = false;
+  auto it = decoder.field_int32(&parse_error);
+  // The decoder doesn't return a parse error (maybe it should, but that has
+  // been the behavior since the beginning).
+  ASSERT_FALSE(parse_error);
+  // But the iterator returns 0 elements.
+  EXPECT_FALSE(it);
+}
+
 }  // namespace
 }  // namespace protozero
diff --git a/src/shared_lib/test/BUILD.gn b/src/shared_lib/test/BUILD.gn
index 220439a..aa87956 100644
--- a/src/shared_lib/test/BUILD.gn
+++ b/src/shared_lib/test/BUILD.gn
@@ -22,7 +22,11 @@
     "../../../gn:gtest_and_gmock",
     "../../../include/perfetto/public",
   ]
-  sources = [ "utils.cc" ]
+  sources = [
+    "utils.cc",
+    "utils.h",
+  ]
+  defines = [ "PERFETTO_SDK_DISABLE_SHLIB_EXPORT" ]
 }
 
 if (enable_perfetto_benchmarks) {
@@ -35,6 +39,7 @@
       "../../../gn:default_deps",
       "../../../include/perfetto/public",
     ]
+    defines = [ "PERFETTO_SDK_DISABLE_SHLIB_EXPORT" ]
     sources = [ "benchmark.cc" ]
   }
 }
@@ -49,6 +54,7 @@
       "../../../gn:gtest_and_gmock",
       "../../../include/perfetto/public",
     ]
+    defines = [ "PERFETTO_SDK_DISABLE_SHLIB_EXPORT" ]
     sources = [ "api_integrationtest.cc" ]
   }
 }
diff --git a/src/tools/ftrace_proto_gen/event_list b/src/tools/ftrace_proto_gen/event_list
index 33ec8e9..40dd949 100644
--- a/src/tools/ftrace_proto_gen/event_list
+++ b/src/tools/ftrace_proto_gen/event_list
@@ -476,3 +476,5 @@
 hyp/host_smc
 hyp/host_mem_abort
 synthetic/suspend_resume_minimal
+mali/mali_CSF_INTERRUPT_START
+mali/mali_CSF_INTERRUPT_END
diff --git a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
index f692373..712e3d1 100644
--- a/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
+++ b/src/tools/ftrace_proto_gen/ftrace_proto_gen.cc
@@ -191,6 +191,11 @@
     // configurations)
     if (group == "ftrace" && proto.event_name == "print" && field->name == "ip")
       continue;
+    // Ignore the "nid" field. On new kernels, this field has a type that we
+    // don't know how to parse. See b/281660544
+    if (group == "f2fs" && proto.event_name == "f2fs_truncate_partial_nodes" &&
+        field->name == "nid")
+      continue;
     s += "{";
     s += "kUnsetOffset, ";
     s += "kUnsetSize, ";
diff --git a/src/tools/proto_filter/proto_filter.cc b/src/tools/proto_filter/proto_filter.cc
index 04385c2..7bfcb74 100644
--- a/src/tools/proto_filter/proto_filter.cc
+++ b/src/tools/proto_filter/proto_filter.cc
@@ -49,7 +49,7 @@
 # Generate the filter bytecode from a .proto schema
 
   proto_filter -r perfetto.protos.Trace -s protos/perfetto/trace/trace.proto \
-               -F /tmp/bytecode [--dedupe]
+               -F /tmp/bytecode [--dedupe] [-x protos.Message:field_to_pass]
 
 # List the used/filtered fields from a trace file
 
@@ -72,14 +72,15 @@
       {"help", no_argument, nullptr, 'h'},
       {"version", no_argument, nullptr, 'v'},
       {"dedupe", no_argument, nullptr, 'd'},
-      {"proto_path", no_argument, nullptr, 'I'},
-      {"schema_in", no_argument, nullptr, 's'},
-      {"root_message", no_argument, nullptr, 'r'},
-      {"msg_in", no_argument, nullptr, 'i'},
-      {"msg_out", no_argument, nullptr, 'o'},
-      {"filter_in", no_argument, nullptr, 'f'},
-      {"filter_out", no_argument, nullptr, 'F'},
-      {"filter_oct_out", no_argument, nullptr, 'T'},
+      {"proto_path", required_argument, nullptr, 'I'},
+      {"schema_in", required_argument, nullptr, 's'},
+      {"root_message", required_argument, nullptr, 'r'},
+      {"msg_in", required_argument, nullptr, 'i'},
+      {"msg_out", required_argument, nullptr, 'o'},
+      {"filter_in", required_argument, nullptr, 'f'},
+      {"filter_out", required_argument, nullptr, 'F'},
+      {"filter_oct_out", required_argument, nullptr, 'T'},
+      {"passthrough", required_argument, nullptr, 'x'},
       {nullptr, 0, nullptr, 0}};
 
   std::string msg_in;
@@ -90,11 +91,12 @@
   std::string filter_oct_out;
   std::string proto_path;
   std::string root_message_arg;
+  std::set<std::string> passthrough_fields;
   bool dedupe = false;
 
   for (;;) {
     int option =
-        getopt_long(argc, argv, "hvdI:s:r:i:o:f:F:T:", long_options, nullptr);
+        getopt_long(argc, argv, "hvdI:s:r:i:o:f:F:T:x:", long_options, nullptr);
 
     if (option == -1)
       break;  // EOF.
@@ -149,6 +151,11 @@
       continue;
     }
 
+    if (option == 'x') {
+      passthrough_fields.insert(optarg);
+      continue;
+    }
+
     if (option == 'h') {
       fprintf(stdout, kUsage);
       exit(0);
@@ -175,8 +182,8 @@
   protozero::FilterUtil filter;
   if (!schema_in.empty()) {
     PERFETTO_LOG("Loading proto schema from %s", schema_in.c_str());
-    if (!filter.LoadMessageDefinition(schema_in, root_message_arg,
-                                      proto_path)) {
+    if (!filter.LoadMessageDefinition(schema_in, root_message_arg, proto_path,
+                                      passthrough_fields)) {
       PERFETTO_ELOG("Failed to parse proto schema from %s", schema_in.c_str());
       return 1;
     }
@@ -264,7 +271,7 @@
   if (!msg_out.empty()) {
     PERFETTO_LOG("Writing filtered proto bytes (%zu bytes) into %s",
                  msg_filtered_data.size(), msg_out.c_str());
-    auto fd = base::OpenFile(msg_out, O_WRONLY | O_CREAT, 0644);
+    auto fd = base::OpenFile(msg_out, O_WRONLY | O_TRUNC | O_CREAT, 0644);
     base::WriteAll(*fd, msg_filtered_data.data(), msg_filtered_data.size());
   }
 
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 29bd8c5..6f0660c 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -172,6 +172,7 @@
       "prelude/functions",
       "prelude/operators",
       "prelude/table_functions",
+      "prelude/tables_views",
       "sqlite",
       "stdlib:gen_amalgamated_stdlib",
       "storage",
diff --git a/src/trace_processor/containers/bit_vector.cc b/src/trace_processor/containers/bit_vector.cc
index b5c2dc9..3e3adab 100644
--- a/src/trace_processor/containers/bit_vector.cc
+++ b/src/trace_processor/containers/bit_vector.cc
@@ -88,6 +88,76 @@
     words_.resize(words_.size() + 8 - (words_.size() % 8u));
 }
 
+void BitVector::Resize(uint32_t new_size, bool filler) {
+  uint32_t old_size = size_;
+  if (new_size == old_size)
+    return;
+
+  // Empty bitvectors should be memory efficient so we don't keep any data
+  // around in the bitvector.
+  if (new_size == 0) {
+    words_.clear();
+    counts_.clear();
+    size_ = 0;
+    return;
+  }
+
+  // Compute the address of the new last bit in the bitvector.
+  Address last_addr = IndexToAddress(new_size - 1);
+  uint32_t old_blocks_size = static_cast<uint32_t>(counts_.size());
+  uint32_t new_blocks_size = last_addr.block_idx + 1;
+
+  // Resize the block and count vectors to have the correct number of entries.
+  words_.resize(Block::kWords * new_blocks_size);
+  counts_.resize(new_blocks_size);
+
+  if (new_size > old_size) {
+    if (filler) {
+      // If the new space should be filled with ones, then set all the bits
+      // between the address of the old size and the new last address.
+      const Address& start = IndexToAddress(old_size);
+      Set(start, last_addr);
+
+      // We then need to update the counts vector to match the changes we
+      // made to the blocks.
+
+      // We start by adding the bits we set in the first block to the
+      // cummulative count before the range we changed.
+      Address end_of_block = {start.block_idx,
+                              {Block::kWords - 1, BitWord::kBits - 1}};
+      uint32_t count_in_block_after_end =
+          AddressToIndex(end_of_block) - AddressToIndex(start) + 1;
+      uint32_t set_count = CountSetBits() + count_in_block_after_end;
+
+      for (uint32_t i = start.block_idx + 1; i <= last_addr.block_idx; ++i) {
+        // Set the count to the cummulative count so far.
+        counts_[i] = set_count;
+
+        // Add a full block of set bits to the count.
+        set_count += Block::kBits;
+      }
+    } else {
+      // If the newly added bits are false, we just need to update the
+      // counts vector with the current size of the bitvector for all
+      // the newly added blocks.
+      if (new_blocks_size > old_blocks_size) {
+        uint32_t count = CountSetBits();
+        for (uint32_t i = old_blocks_size; i < new_blocks_size; ++i) {
+          counts_[i] = count;
+        }
+      }
+    }
+  } else {
+    // Throw away all the bits after the new last bit. We do this to make
+    // future lookup, append and resize operations not have to worrying about
+    // trailing garbage bits in the last block.
+    BlockFromIndex(last_addr.block_idx).ClearAfter(last_addr.block_offset);
+  }
+
+  // Actually update the size.
+  size_ = new_size;
+}
+
 BitVector BitVector::Copy() const {
   return BitVector(words_, counts_, size_);
 }
@@ -100,27 +170,50 @@
   return SetBitsIterator(this);
 }
 
+BitVector BitVector::Not() const {
+  Builder builder(size());
+
+  // Append all words from all blocks except the last one.
+  uint32_t full_words = builder.BitsInCompleteWordsUntilFull();
+  for (uint32_t i = 0; i < full_words; ++i) {
+    builder.AppendWord(ConstBitWord(&words_[i]).Not());
+  }
+
+  // Append bits from the last word.
+  uint32_t bits_from_last_word = builder.BitsUntilFull();
+  ConstBitWord last_word(&words_[full_words]);
+  for (uint32_t i = 0; i < bits_from_last_word; ++i) {
+    builder.Append(!last_word.IsSet(i));
+  }
+
+  return std::move(builder).Build();
+}
+
 void BitVector::UpdateSetBits(const BitVector& update) {
+  if (update.CountSetBits() == 0 || CountSetBits() == 0) {
+    *this = BitVector();
+    return;
+  }
   PERFETTO_DCHECK(update.size() <= CountSetBits());
 
   // Get the start and end ptrs for the current bitvector.
   // Safe because of the static_assert above.
-  auto* ptr = reinterpret_cast<uint64_t*>(words_.data());
-  const uint64_t* ptr_end = ptr + WordCeil(size());
+  uint64_t* ptr = words_.data();
+  const uint64_t* ptr_end = ptr + WordCount(size());
 
-  // Get the start and end ptrs for the current bitvector.
+  // Get the start and end ptrs for the update bitvector.
   // Safe because of the static_assert above.
-  auto* update_ptr = reinterpret_cast<const uint64_t*>(update.words_.data());
-  const uint64_t* update_ptr_end = update_ptr + WordCeil(update.size());
+  const uint64_t* update_ptr = update.words_.data();
+  const uint64_t* update_ptr_end = update_ptr + WordCount(update.size());
 
   // |update_unused_bits| contains |unused_bits_count| bits at the bottom
-  // which indicates the how the next |unused_bits_count| set bits in |this|
+  // which indicates how the next |unused_bits_count| set bits in |this|
   // should be changed. This is necessary because word boundaries in |this| will
   // almost always *not* match the word boundaries in |update|.
   uint64_t update_unused_bits = 0;
   uint8_t unused_bits_count = 0;
 
-  // The basic premise of this loop is, for each word in |this| we find the
+  // The basic premise of this loop is, for each word in |this| we find
   // enough bits from |update| to cover every set bit in the word. We then use
   // the PDEP x64 instruction (or equivalent instructions/software emulation) to
   // update the word and store it back in |this|.
@@ -190,5 +283,47 @@
   PERFETTO_DCHECK(update.CountSetBits() == CountSetBits());
 }
 
+BitVector BitVector::IntersectRange(uint32_t range_start,
+                                    uint32_t range_end) const {
+  uint32_t total_set_bits = CountSetBits();
+  if (total_set_bits == 0 || range_start >= range_end)
+    return BitVector();
+
+  // We should skip all bits until the index of first set bit bigger than
+  // |range_start|.
+  uint32_t start_idx = std::max(range_start, IndexOfNthSet(0));
+  uint32_t end_idx = std::min(range_end, size());
+
+  if (start_idx >= end_idx)
+    return BitVector();
+
+  Builder builder(end_idx);
+
+  // All bits before start should be empty.
+  builder.Skip(start_idx);
+
+  uint32_t front_bits = builder.BitsUntilWordBoundaryOrFull();
+  uint32_t cur_index = start_idx;
+  for (uint32_t i = 0; i < front_bits; ++i, ++cur_index) {
+    builder.Append(IsSet(cur_index));
+  }
+
+  PERFETTO_DCHECK(cur_index == end_idx || cur_index % BitWord::kBits == 0);
+  uint32_t cur_words = cur_index / BitWord::kBits;
+  uint32_t full_words = builder.BitsInCompleteWordsUntilFull() / BitWord::kBits;
+  uint32_t total_full_words = cur_words + full_words;
+  for (; cur_words < total_full_words; ++cur_words) {
+    builder.AppendWord(words_[cur_words]);
+  }
+
+  uint32_t last_bits = builder.BitsUntilFull();
+  cur_index += full_words * BitWord::kBits;
+  for (uint32_t i = 0; i < last_bits; ++i, ++cur_index) {
+    builder.Append(IsSet(cur_index));
+  }
+
+  return std::move(builder).Build();
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/containers/bit_vector.h b/src/trace_processor/containers/bit_vector.h
index 2855739..a9a5fbd 100644
--- a/src/trace_processor/containers/bit_vector.h
+++ b/src/trace_processor/containers/bit_vector.h
@@ -23,6 +23,7 @@
 
 #include <algorithm>
 #include <array>
+#include <optional>
 #include <vector>
 
 #include "perfetto/base/logging.h"
@@ -45,13 +46,15 @@
   using AllBitsIterator = internal::AllBitsIterator;
   using SetBitsIterator = internal::SetBitsIterator;
 
+  static constexpr uint32_t kBitsInWord = 64;
+
   // Builder class which allows efficiently creating a BitVector by appending
   // words. Using this class is generally far more efficient than trying to set
   // bits directly in a BitVector or even appending one bit at a time.
   class Builder {
    public:
     // Creates a Builder for building a BitVector of |size| bits.
-    explicit Builder(uint32_t size) : words_(WordCeil(size)), size_(size) {}
+    explicit Builder(uint32_t size) : words_(WordCount(size)), size_(size) {}
 
     // Skips forward |n| bits leaving them as zeroed.
     void Skip(uint32_t n) {
@@ -86,17 +89,10 @@
       std::vector<uint32_t> counts(no_blocks);
 
       // Calculate counts only for full blocks.
-      for (uint32_t i = 1; i < no_blocks - 1; ++i) {
-        counts[i] +=
-            counts[i - 1] +
-            ConstBlock(&words_[Block::kWords * (i - 1)]).CountSetBits();
+      for (uint32_t i = 1; i < no_blocks; ++i) {
+        counts[i] = counts[i - 1] +
+                    ConstBlock(&words_[Block::kWords * (i - 1)]).CountSetBits();
       }
-      if (size_ % Block::kBits == 0) {
-        counts[no_blocks - 1] +=
-            counts[no_blocks - 2] +
-            ConstBlock(&words_[Block::kWords * (no_blocks - 2)]).CountSetBits();
-      }
-
       return BitVector{std::move(words_), std::move(counts), size_};
     }
 
@@ -104,7 +100,7 @@
     // appended to this builder before having to fallback to |Append| due to
     // being close to the end.
     uint32_t BitsInCompleteWordsUntilFull() {
-      uint32_t next_word = WordCeil(global_bit_offset_);
+      uint32_t next_word = WordCount(global_bit_offset_);
       uint32_t end_word = WordFloor(size_);
       uint32_t complete_words = next_word < end_word ? end_word - next_word : 0;
       return complete_words * BitWord::kBits;
@@ -150,24 +146,7 @@
   BitVector Copy() const;
 
   // Create a bitwise Not copy of the bitvector.
-  BitVector Not() const {
-    Builder builder(size());
-
-    // Append all words from all blocks except the last one.
-    uint32_t full_words = size() / BitWord::kBits;
-    for (uint32_t i = 0; i < full_words; ++i) {
-      builder.AppendWord(ConstBitWord(&words_[i]).Not());
-    }
-
-    // Append bits from the last word.
-    uint32_t bits_from_last_word = size() % BitWord::kBits;
-    ConstBitWord last_word(&words_[full_words]);
-    for (uint32_t i = 0; i < bits_from_last_word; ++i) {
-      builder.Append(!last_word.IsSet(i));
-    }
-
-    return std::move(builder).Build();
-  }
+  BitVector Not() const;
 
   // Returns the size of the bitvector.
   uint32_t size() const { return static_cast<uint32_t>(size_); }
@@ -298,75 +277,7 @@
   // Truncates the BitVector if |size| < |size()| or fills the new space with
   // |filler| if |size| > |size()|. Calling this method is a noop if |size| ==
   // |size()|.
-  void Resize(uint32_t new_size, bool filler = false) {
-    uint32_t old_size = size_;
-    if (new_size == old_size)
-      return;
-
-    // Empty bitvectors should be memory efficient so we don't keep any data
-    // around in the bitvector.
-    if (new_size == 0) {
-      words_.clear();
-      counts_.clear();
-      size_ = 0;
-      return;
-    }
-
-    // Compute the address of the new last bit in the bitvector.
-    Address last_addr = IndexToAddress(new_size - 1);
-    uint32_t old_blocks_size = static_cast<uint32_t>(counts_.size());
-    uint32_t new_blocks_size = last_addr.block_idx + 1;
-
-    // Resize the block and count vectors to have the correct number of entries.
-    words_.resize(Block::kWords * new_blocks_size);
-    counts_.resize(new_blocks_size);
-
-    if (new_size > old_size) {
-      if (filler) {
-        // If the new space should be filled with ones, then set all the bits
-        // between the address of the old size and the new last address.
-        const Address& start = IndexToAddress(old_size);
-        Set(start, last_addr);
-
-        // We then need to update the counts vector to match the changes we
-        // made to the blocks.
-
-        // We start by adding the bits we set in the first block to the
-        // cummulative count before the range we changed.
-        Address end_of_block = {start.block_idx,
-                                {Block::kWords - 1, BitWord::kBits - 1}};
-        uint32_t count_in_block_after_end =
-            AddressToIndex(end_of_block) - AddressToIndex(start) + 1;
-        uint32_t set_count = CountSetBits() + count_in_block_after_end;
-
-        for (uint32_t i = start.block_idx + 1; i <= last_addr.block_idx; ++i) {
-          // Set the count to the cummulative count so far.
-          counts_[i] = set_count;
-
-          // Add a full block of set bits to the count.
-          set_count += Block::kBits;
-        }
-      } else {
-        // If the newly added bits are false, we just need to update the
-        // counts vector with the current size of the bitvector for all
-        // the newly added blocks.
-        if (new_blocks_size > old_blocks_size) {
-          uint32_t count = CountSetBits();
-          for (uint32_t i = old_blocks_size; i < new_blocks_size; ++i) {
-            counts_[i] = count;
-          }
-        }
-      }
-    } else {
-      // Throw away all the bits after the new last bit. We do this to make
-      // future lookup, append and resize operations not have to worrying about
-      // trailing garbage bits in the last block.
-      BlockFromIndex(last_addr.block_idx).ClearAfter(last_addr.block_offset);
-    }
-
-    // Actually update the size.
-    size_ = new_size;
-  }
+  void Resize(uint32_t new_size, bool filler = false);
 
   // Creates a BitVector of size |end| with the bits between |start| and |end|
   // filled by calling the filler function |f(index of bit)|.
@@ -378,7 +289,7 @@
   static BitVector Range(uint32_t start, uint32_t end, Filler f) {
     // Compute the block index and bitvector index where we start and end
     // working one block at a time.
-    uint32_t start_fast_block = BlockCeil(start);
+    uint32_t start_fast_block = BlockCount(start);
     uint32_t start_fast_idx = BlockToIndex(start_fast_block);
     BitVector bv(start, false);
 
@@ -423,6 +334,10 @@
     return bv;
   }
 
+  // Creates a BitVector of size |end| bit the bits between |start| and |end|
+  // filled with corresponding bits |this| BitVector.
+  BitVector IntersectRange(uint32_t range_start, uint32_t range_end) const;
+
   // Requests the removal of unused capacity.
   // Matches the semantics of std::vector::shrink_to_fit.
   void ShrinkToFit() {
@@ -469,7 +384,7 @@
   static constexpr uint32_t ApproxBytesCost(uint32_t n) {
     // The two main things making up a bitvector is the cost of the blocks of
     // bits and the cost of the counts vector.
-    return BlockCeil(n) * Block::kBits + BlockCeil(n) * sizeof(uint32_t);
+    return BlockCount(n) * Block::kBits + BlockCount(n) * sizeof(uint32_t);
   }
 
  private:
@@ -926,11 +841,10 @@
     return idx / BitWord::kBits;
   }
 
-  // Returns the number of words which would be required to store a bit at
-  // |idx|.
-  static uint32_t WordCeil(uint32_t idx) {
-    // See |BlockCeil| for an explanation of this trick.
-    return (idx + BitWord::kBits - 1) / BitWord::kBits;
+  // Returns number of words (int64_t) required to store |bit_count| bits.
+  static uint32_t WordCount(uint32_t bit_count) {
+    // See |BlockCount| for an explanation of this trick.
+    return (bit_count + BitWord::kBits - 1) / BitWord::kBits;
   }
 
   static Address IndexToAddress(uint32_t idx) {
@@ -949,17 +863,16 @@
            addr.block_offset.bit_idx;
   }
 
-  // Rounds |idx| up to the nearest block boundary and returns the block
-  // index. If |idx| is already on a block boundary, the current block is
-  // returned.
+  // Returns number of blocks (arrays of 8 int64_t) required to store
+  // |bit_count| bits.
   //
   // This is useful to be able to find indices where "fast" algorithms can
   // start which work on entire blocks.
-  static constexpr uint32_t BlockCeil(uint32_t idx) {
-    // Adding |Block::kBits - 1| gives us a quick way to get the ceil. We
+  static constexpr uint32_t BlockCount(uint32_t bit_count) {
+    // Adding |Block::kBits - 1| gives us a quick way to get the count. We
     // do this instead of adding 1 at the end because that gives incorrect
-    // answers for index % Block::kBits == 0.
-    return (idx + Block::kBits - 1) / Block::kBits;
+    // answers for bit_count % Block::kBits == 0.
+    return (bit_count + Block::kBits - 1) / Block::kBits;
   }
 
   // Returns the index of the block which would store |idx|.
diff --git a/src/trace_processor/containers/bit_vector_unittest.cc b/src/trace_processor/containers/bit_vector_unittest.cc
index c2e4480..dd8c951 100644
--- a/src/trace_processor/containers/bit_vector_unittest.cc
+++ b/src/trace_processor/containers/bit_vector_unittest.cc
@@ -452,6 +452,63 @@
   ASSERT_FALSE(it);
 }
 
+TEST(BitVectorUnittest, IntersectRange) {
+  BitVector bv = BitVector::Range(1, 20, [](uint32_t t) { return t % 2 == 0; });
+  BitVector intersected = bv.IntersectRange(3, 10);
+
+  ASSERT_EQ(intersected.IndexOfNthSet(0), 4u);
+  ASSERT_EQ(intersected.CountSetBits(), 3u);
+}
+
+TEST(BitVectorUnittest, IntersectRangeFromStart) {
+  BitVector bv = BitVector::Range(1, 20, [](uint32_t t) { return t % 2 == 0; });
+  BitVector intersected = bv.IntersectRange(0, 10);
+
+  ASSERT_EQ(intersected.IndexOfNthSet(0), 2u);
+  ASSERT_EQ(intersected.CountSetBits(), 4u);
+}
+
+TEST(BitVectorUnittest, IntersectRange2) {
+  BitVector bv{true, false, true, true, false, true};
+  BitVector intersected = bv.IntersectRange(2, 4);
+
+  ASSERT_EQ(intersected.IndexOfNthSet(0), 2u);
+}
+
+TEST(BitVectorUnittest, IntersectRangeAfterWord) {
+  BitVector bv =
+      BitVector::Range(64 + 1, 64 + 20, [](uint32_t t) { return t % 2 == 0; });
+  BitVector intersected = bv.IntersectRange(64 + 3, 64 + 10);
+
+  ASSERT_EQ(intersected.IndexOfNthSet(0), 64 + 4u);
+  ASSERT_EQ(intersected.CountSetBits(), 3u);
+}
+
+TEST(BitVectorUnittest, IntersectRangeSetBitsBeforeRange) {
+  BitVector bv = BitVector::Range(10, 30, [](uint32_t t) { return t < 15; });
+  BitVector intersected = bv.IntersectRange(16, 50);
+
+  ASSERT_FALSE(intersected.CountSetBits());
+}
+
+TEST(BitVectorUnittest, IntersectRangeSetBitOnBoundary) {
+  BitVector bv = BitVector(10, false);
+  bv.Set(5);
+  BitVector intersected = bv.IntersectRange(5, 20);
+
+  ASSERT_EQ(intersected.CountSetBits(), 1u);
+  ASSERT_EQ(intersected.IndexOfNthSet(0), 5u);
+}
+
+TEST(BitVectorUnittest, IntersectRangeStressTest) {
+  BitVector bv =
+      BitVector::Range(65, 1024 + 1, [](uint32_t t) { return t % 2 == 0; });
+  BitVector intersected = bv.IntersectRange(30, 500);
+
+  ASSERT_EQ(intersected.IndexOfNthSet(0), 66u);
+  ASSERT_EQ(intersected.CountSetBits(), 217u);
+}
+
 TEST(BitVectorUnittest, Range) {
   BitVector bv = BitVector::Range(1, 9, [](uint32_t t) { return t % 3 == 0; });
   ASSERT_EQ(bv.size(), 9u);
@@ -515,6 +572,22 @@
   ASSERT_FALSE(bv.IsSet(2));
 }
 
+TEST(BitVectorUnittest, BuilderCountSetBits) {
+  // 16 words and 1 bit
+  BitVector::Builder builder(1025);
+
+  // 100100011010001010110011110001001 as a hex literal, with 15 set bits.
+  uint64_t word = 0x123456789;
+  for (uint32_t i = 0; i < 16; ++i) {
+    builder.AppendWord(word);
+  }
+  builder.Append(1);
+  BitVector bv = std::move(builder).Build();
+
+  ASSERT_EQ(bv.CountSetBits(500), 120u);
+  ASSERT_EQ(bv.CountSetBits(), 16 * 15 + 1u);
+}
+
 TEST(BitVectorUnittest, BuilderStressTest) {
   // Space for 128 words and 1 bit
   uint32_t size = 8 * 1024 + 1;
@@ -533,7 +606,7 @@
   ASSERT_EQ(builder.BitsUntilFull(), size - 1024);
   ASSERT_EQ(builder.BitsUntilWordBoundaryOrFull(), 0u);
 
-  // 100100011010001010110011110001001 as a hex literal.
+  // 100100011010001010110011110001001 as a hex literal, with 15 set bits.
   uint64_t word = 0x123456789;
 
   // Add all of the remaining words.
@@ -550,6 +623,8 @@
   builder.Append(1);
 
   BitVector bv = std::move(builder).Build();
+
+  ASSERT_EQ(bv.CountSetBits(), 2681u);
   ASSERT_EQ(bv.size(), 8u * 1024u + 1u);
 
   ASSERT_TRUE(bv.IsSet(0));
diff --git a/src/trace_processor/containers/row_map.cc b/src/trace_processor/containers/row_map.cc
index b3d1d59..d00d487 100644
--- a/src/trace_processor/containers/row_map.cc
+++ b/src/trace_processor/containers/row_map.cc
@@ -15,6 +15,7 @@
  */
 
 #include "src/trace_processor/containers/row_map.h"
+#include <unordered_set>
 
 #include "src/trace_processor/containers/row_map_algorithms.h"
 
@@ -23,22 +24,19 @@
 
 namespace {
 
-RowMap SelectRangeWithRange(uint32_t start,
-                            uint32_t end,
-                            uint32_t selector_start,
-                            uint32_t selector_end) {
-  PERFETTO_DCHECK(start <= end);
-  PERFETTO_DCHECK(selector_start <= selector_end);
-  PERFETTO_DCHECK(selector_end <= end - start);
+using Range = RowMap::Range;
+using OutputIndex = RowMap::OutputIndex;
+using Variant = std::variant<Range, BitVector, std::vector<OutputIndex>>;
 
-  return RowMap(start + selector_start, start + selector_end);
+RowMap Select(Range range, Range selector) {
+  PERFETTO_DCHECK(selector.start <= selector.end);
+  PERFETTO_DCHECK(selector.end <= range.size());
+
+  return RowMap(range.start + selector.start, range.start + selector.end);
 }
 
-RowMap SelectRangeWithBv(uint32_t start,
-                         uint32_t end,
-                         const BitVector& selector) {
-  PERFETTO_DCHECK(start <= end);
-  PERFETTO_DCHECK(selector.size() <= end - start);
+RowMap Select(Range range, const BitVector& selector) {
+  PERFETTO_DCHECK(selector.size() <= range.size());
 
   // If |start| == 0 and |selector.size()| <= |end - start| (which is a
   // precondition for this function), the BitVector we generate is going to be
@@ -48,60 +46,52 @@
   // SelectRows is called on all the table RowMaps with a BitVector. The self
   // RowMap will always be a range so we expect this case to be hit at least
   // once every filter operation.
-  if (start == 0u)
+  if (range.start == 0u)
     return RowMap(selector.Copy());
 
   // We only need to resize to |start| + |selector.size()| as we know any rows
   // not covered by |selector| are going to be removed below.
-  BitVector bv(start, false);
-  bv.Resize(start + selector.size(), true);
+  BitVector bv(range.start, false);
+  bv.Resize(range.start + selector.size(), true);
 
   bv.UpdateSetBits(selector);
   return RowMap(std::move(bv));
 }
 
-RowMap SelectRangeWithIv(uint32_t start,
-                         uint32_t end,
-                         const std::vector<uint32_t>& selector) {
-  PERFETTO_DCHECK(start <= end);
-
+RowMap Select(Range range, const std::vector<OutputIndex>& selector) {
   std::vector<uint32_t> iv(selector.size());
   for (uint32_t i = 0; i < selector.size(); ++i) {
-    PERFETTO_DCHECK(selector[i] < end - start);
-    iv[i] = selector[i] + start;
+    PERFETTO_DCHECK(selector[i] < range.size());
+    iv[i] = selector[i] + range.start;
   }
   return RowMap(std::move(iv));
 }
 
-RowMap SelectBvWithRange(const BitVector& bv,
-                         uint32_t selector_start,
-                         uint32_t selector_end) {
-  PERFETTO_DCHECK(selector_start <= selector_end);
-  PERFETTO_DCHECK(selector_end <= bv.CountSetBits());
+RowMap Select(const BitVector& bv, Range selector) {
+  PERFETTO_DCHECK(selector.end <= bv.CountSetBits());
 
   // If we're simply selecting every element in the bitvector, just
   // return a copy of the BitVector without iterating.
   BitVector ret = bv.Copy();
-  if (selector_start == 0 && selector_end == bv.CountSetBits()) {
+  if (selector.start == 0 && selector.end == bv.CountSetBits()) {
     return RowMap(std::move(ret));
   }
 
   for (auto it = ret.IterateSetBits(); it; it.Next()) {
     auto set_idx = it.ordinal();
-    if (set_idx < selector_start || set_idx >= selector_end)
+    if (set_idx < selector.start || set_idx >= selector.end)
       it.Clear();
   }
   return RowMap(std::move(ret));
 }
 
-RowMap SelectBvWithBv(const BitVector& bv, const BitVector& selector) {
+RowMap Select(const BitVector& bv, const BitVector& selector) {
   BitVector ret = bv.Copy();
   ret.UpdateSetBits(selector);
   return RowMap(std::move(ret));
 }
 
-RowMap SelectBvWithIv(const BitVector& bv,
-                      const std::vector<uint32_t>& selector) {
+RowMap Select(const BitVector& bv, const std::vector<uint32_t>& selector) {
   // The value of this constant was found by considering the benchmarks
   // |BM_SelectBvWithIvByConvertToIv| and |BM_SelectBvWithIvByIndexOfNthSet|.
   //
@@ -130,21 +120,17 @@
       row_map_algorithms::SelectBvWithIvByIndexOfNthSet(bv, selector));
 }
 
-RowMap SelectIvWithRange(const std::vector<uint32_t>& iv,
-                         uint32_t selector_start,
-                         uint32_t selector_end) {
-  PERFETTO_DCHECK(selector_start <= selector_end);
-  PERFETTO_DCHECK(selector_end <= iv.size());
+RowMap Select(const std::vector<uint32_t>& iv, Range selector) {
+  PERFETTO_DCHECK(selector.end <= iv.size());
 
-  std::vector<uint32_t> ret(selector_end - selector_start);
-  for (uint32_t i = selector_start; i < selector_end; ++i) {
-    ret[i - selector_start] = iv[i];
+  std::vector<uint32_t> ret(selector.size());
+  for (uint32_t i = selector.start; i < selector.end; ++i) {
+    ret[i - selector.start] = iv[i];
   }
   return RowMap(std::move(ret));
 }
 
-RowMap SelectIvWithBv(const std::vector<uint32_t>& iv,
-                      const BitVector& selector) {
+RowMap Select(const std::vector<uint32_t>& iv, const BitVector& selector) {
   PERFETTO_DCHECK(selector.size() <= iv.size());
 
   std::vector<uint32_t> copy = iv;
@@ -158,83 +144,133 @@
   return RowMap(std::move(copy));
 }
 
-RowMap SelectIvWithIv(const std::vector<uint32_t>& iv,
-                      const std::vector<uint32_t>& selector) {
+RowMap Select(const std::vector<uint32_t>& iv,
+              const std::vector<uint32_t>& selector) {
   return RowMap(row_map_algorithms::SelectIvWithIv(iv, selector));
 }
 
+Variant IntersectInternal(BitVector& first, const BitVector& second) {
+  for (auto set_bit = first.IterateSetBits(); set_bit; set_bit.Next()) {
+    if (!second.IsSet(set_bit.index()))
+      set_bit.Clear();
+  }
+  return std::move(first);
+}
+
+Variant IntersectInternal(Range first, Range second) {
+  // If both RowMaps have ranges, we can just take the smallest intersection
+  // of them as the new RowMap.
+  // We have this as an explicit fast path as this is very common for
+  // constraints on id and sorted columns to satisfy this condition.
+  OutputIndex start = std::max(first.start, second.start);
+  OutputIndex end = std::max(start, std::min(first.end, second.end));
+  return Range{start, end};
+}
+
+Variant IntersectInternal(std::vector<OutputIndex>& first,
+                          const std::vector<OutputIndex>& second) {
+  std::unordered_set<OutputIndex> lookup(second.begin(), second.end());
+  first.erase(std::remove_if(first.begin(), first.end(),
+                             [lookup](OutputIndex ind) {
+                               return lookup.find(ind) == lookup.end();
+                             }),
+              first.end());
+  return std::move(first);
+}
+
+Variant IntersectInternal(Range range, const BitVector& bv) {
+  return bv.IntersectRange(range.start, range.end);
+}
+
+Variant IntersectInternal(BitVector& bv, Range range) {
+  return IntersectInternal(range, bv);
+}
+
+Variant IntersectInternal(const std::vector<OutputIndex>& index_vec,
+                          const BitVector& bv) {
+  std::vector<OutputIndex> new_vec(index_vec.begin(), index_vec.end());
+  new_vec.erase(std::remove_if(new_vec.begin(), new_vec.end(),
+                               [&bv](uint32_t i) { return !bv.IsSet(i); }),
+                new_vec.end());
+  return std::move(new_vec);
+}
+
+Variant IntersectInternal(const BitVector& bv,
+                          const std::vector<OutputIndex>& index_vec) {
+  return IntersectInternal(index_vec, bv);
+}
+
+Variant IntersectInternal(Range range,
+                          const std::vector<OutputIndex>& index_vec) {
+  std::vector<OutputIndex> new_vec(index_vec.begin(), index_vec.end());
+  new_vec.erase(std::remove_if(new_vec.begin(), new_vec.end(),
+                               [range](uint32_t i) {
+                                 return i < range.start || i >= range.end;
+                               }),
+                new_vec.end());
+  return std::move(new_vec);
+}
+
+Variant IntersectInternal(const std::vector<OutputIndex>& index_vec,
+                          Range range) {
+  return IntersectInternal(range, index_vec);
+}
+
 }  // namespace
 
-RowMap::RowMap() : RowMap(0, 0) {}
+RowMap::RowMap() : RowMap(Range()) {}
 
 RowMap::RowMap(uint32_t start, uint32_t end, OptimizeFor optimize_for)
-    : mode_(Mode::kRange),
-      start_index_(start),
-      end_index_(end),
-      optimize_for_(optimize_for) {}
+    : data_(Range{start, end}), optimize_for_(optimize_for) {}
 
-RowMap::RowMap(BitVector bit_vector)
-    : mode_(Mode::kBitVector), bit_vector_(std::move(bit_vector)) {}
+RowMap::RowMap(Variant def) : data_(std::move(def)) {}
 
-RowMap::RowMap(std::vector<uint32_t> vec)
-    : mode_(Mode::kIndexVector), index_vector_(std::move(vec)) {}
+RowMap::RowMap(Range r) : data_(r) {}
+
+// Creates a RowMap backed by a BitVector.
+RowMap::RowMap(BitVector bit_vector) : data_(std::move(bit_vector)) {}
+
+// Creates a RowMap backed by an std::vector<uint32_t>.
+RowMap::RowMap(IndexVector vec) : data_(vec) {}
 
 RowMap RowMap::Copy() const {
-  switch (mode_) {
-    case Mode::kRange:
-      return RowMap(start_index_, end_index_);
-    case Mode::kBitVector:
-      return RowMap(bit_vector_.Copy());
-    case Mode::kIndexVector:
-      return RowMap(index_vector_);
+  if (auto* range = std::get_if<Range>(&data_)) {
+    return RowMap(*range);
   }
-  PERFETTO_FATAL("For GCC");
+  if (auto* bv = std::get_if<BitVector>(&data_)) {
+    return RowMap(bv->Copy());
+  }
+  if (auto* vec = std::get_if<IndexVector>(&data_)) {
+    return RowMap(*vec);
+  }
+  NoVariantMatched();
 }
 
 RowMap RowMap::SelectRowsSlow(const RowMap& selector) const {
-  // Pick the strategy based on the selector as there is more common code
-  // between selectors of the same mode than between the RowMaps being
-  // selected of the same mode.
-  switch (selector.mode_) {
-    case Mode::kRange:
-      switch (mode_) {
-        case Mode::kRange:
-          return SelectRangeWithRange(start_index_, end_index_,
-                                      selector.start_index_,
-                                      selector.end_index_);
-        case Mode::kBitVector:
-          return SelectBvWithRange(bit_vector_, selector.start_index_,
-                                   selector.end_index_);
-        case Mode::kIndexVector:
-          return SelectIvWithRange(index_vector_, selector.start_index_,
-                                   selector.end_index_);
-      }
-      break;
-    case Mode::kBitVector:
-      switch (mode_) {
-        case Mode::kRange:
-          return SelectRangeWithBv(start_index_, end_index_,
-                                   selector.bit_vector_);
-        case Mode::kBitVector:
-          return SelectBvWithBv(bit_vector_, selector.bit_vector_);
-        case Mode::kIndexVector:
-          return SelectIvWithBv(index_vector_, selector.bit_vector_);
-      }
-      break;
-    case Mode::kIndexVector:
-      switch (mode_) {
-        case Mode::kRange:
-          return SelectRangeWithIv(start_index_, end_index_,
-                                   selector.index_vector_);
-        case Mode::kBitVector:
-          return SelectBvWithIv(bit_vector_, selector.index_vector_);
-        case Mode::kIndexVector:
-          return SelectIvWithIv(index_vector_, selector.index_vector_);
-      }
-      break;
-  }
-  PERFETTO_FATAL("For GCC");
+  return std::visit(
+      [](const auto& def, const auto& selector_def) {
+        return Select(def, selector_def);
+      },
+      data_, selector.data_);
 }
 
+void RowMap::Intersect(const RowMap& second) {
+  data_ = std::visit(
+      [](auto& def, auto& selector_def) {
+        return IntersectInternal(def, selector_def);
+      },
+      data_, second.data_);
+}
+
+RowMap::Iterator::Iterator(const RowMap* rm) : rm_(rm) {
+  if (auto* range = std::get_if<Range>(&rm_->data_)) {
+    ordinal_ = range->start;
+    return;
+  }
+  if (auto* bv = std::get_if<BitVector>(&rm_->data_)) {
+    set_bits_it_.reset(new BitVector::SetBitsIterator(bv->IterateSetBits()));
+    return;
+  }
+}
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/containers/row_map.h b/src/trace_processor/containers/row_map.h
index 8ab3915..283455c 100644
--- a/src/trace_processor/containers/row_map.h
+++ b/src/trace_processor/containers/row_map.h
@@ -21,6 +21,7 @@
 
 #include <memory>
 #include <optional>
+#include <variant>
 #include <vector>
 
 #include "perfetto/base/logging.h"
@@ -73,55 +74,24 @@
 // more efficient than a BitVector; in this case, we will make a best effort
 // switch to it but the cases where this happens is not precisely defined.
 class RowMap {
- private:
-  // We need to declare these iterator classes before RowMap::Iterator as it
-  // depends on them. However, we don't want to make these public so keep them
-  // under a special private section.
-
-  // Iterator for ranged mode of RowMap.
-  // This class should act as a drop-in replacement for
-  // BitVector::SetBitsIterator.
-  class RangeIterator {
-   public:
-    RangeIterator(const RowMap* rm) : rm_(rm), index_(rm->start_index_) {}
-
-    void Next() { ++index_; }
-
-    operator bool() const { return index_ < rm_->end_index_; }
-
-    uint32_t index() const { return index_; }
-
-    uint32_t ordinal() const { return index_ - rm_->start_index_; }
-
-   private:
-    const RowMap* rm_ = nullptr;
-    uint32_t index_ = 0;
-  };
-
-  // Iterator for index vector mode of RowMap.
-  // This class should act as a drop-in replacement for
-  // BitVector::SetBitsIterator.
-  class IndexVectorIterator {
-   public:
-    IndexVectorIterator(const RowMap* rm) : rm_(rm) {}
-
-    void Next() { ++ordinal_; }
-
-    operator bool() const { return ordinal_ < rm_->index_vector_.size(); }
-
-    uint32_t index() const { return rm_->index_vector_[ordinal_]; }
-
-    uint32_t ordinal() const { return ordinal_; }
-
-   private:
-    const RowMap* rm_ = nullptr;
-    uint32_t ordinal_ = 0;
-  };
-
  public:
-  // Input type.
   using InputRow = uint32_t;
   using OutputIndex = uint32_t;
+  using IndexVector = std::vector<OutputIndex>;
+
+  struct Range {
+    Range(OutputIndex start_index, OutputIndex end_index)
+        : start(start_index), end(end_index) {}
+    Range() : start(0), end(0) {}
+
+    OutputIndex start = 0;  // This is an inclusive index.
+    OutputIndex end = 0;    // This is an exclusive index.
+
+    uint32_t size() const {
+      PERFETTO_DCHECK(end >= start);
+      return end - start;
+    }
+  };
 
   // Allows efficient iteration over the rows of a RowMap.
   //
@@ -130,87 +100,72 @@
   // of the RowMap on every method call.
   class Iterator {
    public:
-    Iterator(const RowMap* rm) : rm_(rm) {
-      switch (rm->mode_) {
-        case Mode::kRange:
-          range_it_.reset(new RangeIterator(rm));
-          break;
-        case Mode::kBitVector:
-          set_bits_it_.reset(
-              new BitVector::SetBitsIterator(rm->bit_vector_.IterateSetBits()));
-          break;
-        case Mode::kIndexVector:
-          iv_it_.reset(new IndexVectorIterator(rm));
-          break;
-      }
-    }
+    explicit Iterator(const RowMap* rm);
 
     Iterator(Iterator&&) noexcept = default;
     Iterator& operator=(Iterator&&) = default;
 
     // Forwards the iterator to the next row of the RowMap.
     void Next() {
-      switch (rm_->mode_) {
-        case Mode::kRange:
-          range_it_->Next();
-          break;
-        case Mode::kBitVector:
-          set_bits_it_->Next();
-          break;
-        case Mode::kIndexVector:
-          iv_it_->Next();
-          break;
+      if (std::get_if<Range>(&rm_->data_)) {
+        ++ordinal_;
+      } else if (std::get_if<BitVector>(&rm_->data_)) {
+        set_bits_it_->Next();
+      } else if (std::get_if<IndexVector>(&rm_->data_)) {
+        ++ordinal_;
       }
     }
 
     // Returns if the iterator is still valid.
     operator bool() const {
-      switch (rm_->mode_) {
-        case Mode::kRange:
-          return *range_it_;
-        case Mode::kBitVector:
-          return *set_bits_it_;
-        case Mode::kIndexVector:
-          return *iv_it_;
+      if (auto* range = std::get_if<Range>(&rm_->data_)) {
+        return ordinal_ < range->end;
       }
-      PERFETTO_FATAL("For GCC");
+      if (std::get_if<BitVector>(&rm_->data_)) {
+        return bool(*set_bits_it_);
+      }
+      if (auto* vec = std::get_if<IndexVector>(&rm_->data_)) {
+        return ordinal_ < vec->size();
+      }
+      PERFETTO_FATAL("Didn't match any variant type.");
     }
 
     // Returns the index pointed to by this iterator.
     OutputIndex index() const {
-      switch (rm_->mode_) {
-        case Mode::kRange:
-          return range_it_->index();
-        case Mode::kBitVector:
-          return set_bits_it_->index();
-        case Mode::kIndexVector:
-          return iv_it_->index();
+      if (std::get_if<Range>(&rm_->data_)) {
+        return ordinal_;
       }
-      PERFETTO_FATAL("For GCC");
+      if (std::get_if<BitVector>(&rm_->data_)) {
+        return set_bits_it_->index();
+      }
+      if (auto* vec = std::get_if<IndexVector>(&rm_->data_)) {
+        return (*vec)[ordinal_];
+      }
+      PERFETTO_FATAL("Didn't match any variant type.");
     }
 
     // Returns the row of the index the iterator points to.
     InputRow row() const {
-      switch (rm_->mode_) {
-        case Mode::kRange:
-          return range_it_->ordinal();
-        case Mode::kBitVector:
-          return set_bits_it_->ordinal();
-        case Mode::kIndexVector:
-          return iv_it_->ordinal();
+      if (auto* range = std::get_if<Range>(&rm_->data_)) {
+        return ordinal_ - range->start;
       }
-      PERFETTO_FATAL("For GCC");
+      if (std::get_if<BitVector>(&rm_->data_)) {
+        return set_bits_it_->ordinal();
+      }
+      if (std::get_if<IndexVector>(&rm_->data_)) {
+        return ordinal_;
+      }
+      PERFETTO_FATAL("Didn't match any variant type.");
     }
 
    private:
     Iterator(const Iterator&) = delete;
     Iterator& operator=(const Iterator&) = delete;
 
-    // Only one of the below will be non-null depending on the mode of the
-    // RowMap.
-    std::unique_ptr<RangeIterator> range_it_;
+    // Ordinal will not be used for BitVector based RowMap.
+    uint32_t ordinal_ = 0;
+    // Not nullptr for BitVector based RowMap.
     std::unique_ptr<BitVector::SetBitsIterator> set_bits_it_;
-    std::unique_ptr<IndexVectorIterator> iv_it_;
 
     const RowMap* rm_ = nullptr;
   };
@@ -228,15 +183,15 @@
 
   // Creates a RowMap containing the range of indices between |start| and |end|
   // i.e. all indices between |start| (inclusive) and |end| (exclusive).
-  explicit RowMap(OutputIndex start,
-                  OutputIndex end,
-                  OptimizeFor optimize_for = OptimizeFor::kMemory);
+  RowMap(OutputIndex start,
+         OutputIndex end,
+         OptimizeFor optimize_for = OptimizeFor::kMemory);
 
   // Creates a RowMap backed by a BitVector.
-  explicit RowMap(BitVector bit_vector);
+  explicit RowMap(BitVector);
 
   // Creates a RowMap backed by an std::vector<uint32_t>.
-  explicit RowMap(std::vector<OutputIndex> vec);
+  explicit RowMap(IndexVector);
 
   RowMap(const RowMap&) noexcept = delete;
   RowMap& operator=(const RowMap&) = delete;
@@ -259,15 +214,16 @@
   // Returns the size of the RowMap; that is the number of indices in the
   // RowMap.
   uint32_t size() const {
-    switch (mode_) {
-      case Mode::kRange:
-        return end_index_ - start_index_;
-      case Mode::kBitVector:
-        return bit_vector_.CountSetBits();
-      case Mode::kIndexVector:
-        return static_cast<uint32_t>(index_vector_.size());
+    if (auto* range = std::get_if<Range>(&data_)) {
+      return range->size();
     }
-    PERFETTO_FATAL("For GCC");
+    if (auto* bv = std::get_if<BitVector>(&data_)) {
+      return bv->CountSetBits();
+    }
+    if (auto* vec = std::get_if<IndexVector>(&data_)) {
+      return static_cast<uint32_t>(vec->size());
+    }
+    NoVariantMatched();
   }
 
   // Returns whether this rowmap is empty.
@@ -275,57 +231,51 @@
 
   // Returns the index at the given |row|.
   OutputIndex Get(InputRow row) const {
-    PERFETTO_DCHECK(row < size());
-    switch (mode_) {
-      case Mode::kRange:
-        return GetRange(row);
-      case Mode::kBitVector:
-        return GetBitVector(row);
-      case Mode::kIndexVector:
-        return GetIndexVector(row);
+    if (auto* range = std::get_if<Range>(&data_)) {
+      return GetRange(*range, row);
     }
-    PERFETTO_FATAL("For GCC");
+    if (auto* bv = std::get_if<BitVector>(&data_)) {
+      return GetBitVector(*bv, row);
+    }
+    if (auto* vec = std::get_if<IndexVector>(&data_)) {
+      return GetIndexVector(*vec, row);
+    }
+    NoVariantMatched();
   }
 
   // Returns whether the RowMap contains the given index.
   bool Contains(OutputIndex index) const {
-    switch (mode_) {
-      case Mode::kRange: {
-        return index >= start_index_ && index < end_index_;
-      }
-      case Mode::kBitVector: {
-        return index < bit_vector_.size() && bit_vector_.IsSet(index);
-      }
-      case Mode::kIndexVector: {
-        auto it = std::find(index_vector_.begin(), index_vector_.end(), index);
-        return it != index_vector_.end();
-      }
+    if (auto* range = std::get_if<Range>(&data_)) {
+      return index >= range->start && index < range->end;
     }
-    PERFETTO_FATAL("For GCC");
+    if (auto* bv = std::get_if<BitVector>(&data_)) {
+      return index < bv->size() && bv->IsSet(index);
+    }
+    if (auto* vec = std::get_if<IndexVector>(&data_)) {
+      return std::find(vec->begin(), vec->end(), index) != vec->end();
+    }
+    NoVariantMatched();
   }
 
   // Returns the first row of the given |index| in the RowMap.
   std::optional<InputRow> RowOf(OutputIndex index) const {
-    switch (mode_) {
-      case Mode::kRange: {
-        if (index < start_index_ || index >= end_index_)
-          return std::nullopt;
-        return index - start_index_;
-      }
-      case Mode::kBitVector: {
-        return index < bit_vector_.size() && bit_vector_.IsSet(index)
-                   ? std::make_optional(bit_vector_.CountSetBits(index))
-                   : std::nullopt;
-      }
-      case Mode::kIndexVector: {
-        auto it = std::find(index_vector_.begin(), index_vector_.end(), index);
-        return it != index_vector_.end()
-                   ? std::make_optional(static_cast<InputRow>(
-                         std::distance(index_vector_.begin(), it)))
-                   : std::nullopt;
-      }
+    if (auto* range = std::get_if<Range>(&data_)) {
+      if (index < range->start || index >= range->end)
+        return std::nullopt;
+      return index - range->start;
     }
-    PERFETTO_FATAL("For GCC");
+    if (auto* bv = std::get_if<BitVector>(&data_)) {
+      return index < bv->size() && bv->IsSet(index)
+                 ? std::make_optional(bv->CountSetBits(index))
+                 : std::nullopt;
+    }
+    if (auto* vec = std::get_if<IndexVector>(&data_)) {
+      auto it = std::find(vec->begin(), vec->end(), index);
+      return it != vec->end() ? std::make_optional(static_cast<InputRow>(
+                                    std::distance(vec->begin(), it)))
+                              : std::nullopt;
+    }
+    NoVariantMatched();
   }
 
   // Performs an ordered insert of the index into the current RowMap
@@ -343,34 +293,36 @@
   // the RowMap is in range or BitVector mode but is a required condition for
   // IndexVector mode.
   void Insert(OutputIndex index) {
-    switch (mode_) {
-      case Mode::kRange:
-        if (index == end_index_) {
-          // Fast path: if we're just appending to the end of the range, we can
-          // stay in range mode and just bump the end index.
-          end_index_++;
-        } else {
-          // Slow path: the insert is somewhere else other than the end. This
-          // means we need to switch to using a BitVector instead.
-          bit_vector_.Resize(start_index_, false);
-          bit_vector_.Resize(end_index_, true);
-          *this = RowMap(std::move(bit_vector_));
-
-          InsertIntoBitVector(index);
-        }
-        break;
-      case Mode::kBitVector:
-        InsertIntoBitVector(index);
-        break;
-      case Mode::kIndexVector: {
-        PERFETTO_DCHECK(
-            std::is_sorted(index_vector_.begin(), index_vector_.end()));
-        auto it =
-            std::upper_bound(index_vector_.begin(), index_vector_.end(), index);
-        index_vector_.insert(it, index);
-        break;
+    if (auto* range = std::get_if<Range>(&data_)) {
+      if (index == range->end) {
+        // Fast path: if we're just appending to the end
+        // of the range, we can stay in range mode and
+        // just bump the end index.
+        range->end++;
+        return;
       }
+
+      // Slow path: the insert is somewhere else other
+      // than the end. This means we need to switch to
+      // using a BitVector instead.
+      BitVector bv;
+      bv.Resize(range->start, false);
+      bv.Resize(range->end, true);
+      InsertIntoBitVector(bv, index);
+      data_ = std::move(bv);
+      return;
     }
+    if (auto* bv = std::get_if<BitVector>(&data_)) {
+      InsertIntoBitVector(*bv, index);
+      return;
+    }
+    if (auto* vec = std::get_if<IndexVector>(&data_)) {
+      PERFETTO_DCHECK(std::is_sorted(vec->begin(), vec->end()));
+      auto it = std::upper_bound(vec->begin(), vec->end(), index);
+      vec->insert(it, index);
+      return;
+    }
+    NoVariantMatched();
   }
 
   // Updates this RowMap by 'picking' the indices given by |picker|.
@@ -413,22 +365,7 @@
   //   if (start_index <= idx && idx < end_index)
   //     continue;
   //   Remove(idx)
-  void Intersect(uint32_t start_index, uint32_t end_index) {
-    if (mode_ == Mode::kRange) {
-      // If both RowMaps have ranges, we can just take the smallest intersection
-      // of them as the new RowMap.
-      // We have this as an explicit fast path as this is very common for
-      // constraints on id and sorted columns to satisfy this condition.
-      start_index_ = std::max(start_index_, start_index);
-      end_index_ = std::max(start_index_, std::min(end_index_, end_index));
-      return;
-    }
-
-    // TODO(lalitm): improve efficiency of this if we end up needing it.
-    Filter([start_index, end_index](OutputIndex index) {
-      return index >= start_index && index < end_index;
-    });
-  }
+  void Intersect(const RowMap& second);
 
   // Intersects this RowMap with |index|. If this RowMap contained |index|, then
   // it will *only* contain |index|. Otherwise, it will be empty.
@@ -444,71 +381,82 @@
   void Clear() { *this = RowMap(); }
 
   template <typename Comparator = bool(uint32_t, uint32_t)>
-  void StableSort(std::vector<uint32_t>* out, Comparator c) const {
-    switch (mode_) {
-      case Mode::kRange:
-        std::stable_sort(out->begin(), out->end(),
-                         [this, c](uint32_t a, uint32_t b) {
-                           return c(GetRange(a), GetRange(b));
-                         });
-        break;
-      case Mode::kBitVector:
-        std::stable_sort(out->begin(), out->end(),
-                         [this, c](uint32_t a, uint32_t b) {
-                           return c(GetBitVector(a), GetBitVector(b));
-                         });
-        break;
-      case Mode::kIndexVector:
-        std::stable_sort(out->begin(), out->end(),
-                         [this, c](uint32_t a, uint32_t b) {
-                           return c(GetIndexVector(a), GetIndexVector(b));
-                         });
-        break;
+  void StableSort(IndexVector* out, Comparator c) const {
+    if (auto* range = std::get_if<Range>(&data_)) {
+      std::stable_sort(out->begin(), out->end(),
+                       [range, c](uint32_t a, uint32_t b) {
+                         return c(GetRange(*range, a), GetRange(*range, b));
+                       });
+      return;
     }
+    if (auto* bv = std::get_if<BitVector>(&data_)) {
+      std::stable_sort(out->begin(), out->end(),
+                       [&bv, c](uint32_t a, uint32_t b) {
+                         return c(GetBitVector(*bv, a), GetBitVector(*bv, b));
+                       });
+      return;
+    }
+    if (auto* vec = std::get_if<IndexVector>(&data_)) {
+      std::stable_sort(
+          out->begin(), out->end(), [vec, c](uint32_t a, uint32_t b) {
+            return c(GetIndexVector(*vec, a), GetIndexVector(*vec, b));
+          });
+      return;
+    }
+    NoVariantMatched();
   }
 
   // Filters the indices in |out| by keeping those which meet |p|.
   template <typename Predicate = bool(OutputIndex)>
   void Filter(Predicate p) {
-    switch (mode_) {
-      case Mode::kRange:
-        FilterRange(p);
-        break;
-      case Mode::kBitVector: {
-        for (auto it = bit_vector_.IterateSetBits(); it; it.Next()) {
-          if (!p(it.index()))
-            it.Clear();
-        }
-        break;
-      }
-      case Mode::kIndexVector: {
-        auto ret = std::remove_if(index_vector_.begin(), index_vector_.end(),
-                                  [p](uint32_t i) { return !p(i); });
-        index_vector_.erase(ret, index_vector_.end());
-        break;
-      }
+    if (auto* range = std::get_if<Range>(&data_)) {
+      data_ = FilterRange(p, *range);
+      return;
     }
+    if (auto* bv = std::get_if<BitVector>(&data_)) {
+      for (auto it = bv->IterateSetBits(); it; it.Next()) {
+        if (!p(it.index()))
+          it.Clear();
+      }
+      return;
+    }
+    if (auto* vec = std::get_if<IndexVector>(&data_)) {
+      auto ret = std::remove_if(vec->begin(), vec->end(),
+                                [p](uint32_t i) { return !p(i); });
+      vec->erase(ret, vec->end());
+      return;
+    }
+    NoVariantMatched();
   }
 
   // Returns the iterator over the rows in this RowMap.
   Iterator IterateRows() const { return Iterator(this); }
 
   // Returns if the RowMap is internally represented using a range.
-  bool IsRange() const { return mode_ == Mode::kRange; }
+  bool IsRange() const { return std::holds_alternative<Range>(data_); }
+
+  // Returns if the RowMap is internally represented using a BitVector.
+  bool IsBitVector() const { return std::holds_alternative<BitVector>(data_); }
+
+  // Returns if the RowMap is internally represented using an index vector.
+  bool IsIndexVector() const {
+    return std::holds_alternative<IndexVector>(data_);
+  }
 
  private:
-  enum class Mode {
-    kRange,
-    kBitVector,
-    kIndexVector,
-  };
+  using Variant = std::variant<Range, BitVector, IndexVector>;
+
+  explicit RowMap(Range);
+
+  explicit RowMap(Variant);
+
   // TODO(lalitm): remove this when the coupling between RowMap and
   // ColumnStorage Selector is broken (after filtering is moved out of here).
   friend class ColumnStorageOverlay;
 
   template <typename Predicate>
-  void FilterRange(Predicate p) {
-    uint32_t count = end_index_ - start_index_;
+  Variant FilterRange(Predicate p, Range r) {
+    uint32_t count = r.size();
 
     // Optimization: if we are only going to scan a few indices, it's not
     // worth the haslle of working with a BitVector.
@@ -517,7 +465,7 @@
 
     // Optimization: weif the cost of a BitVector is more than the highest
     // possible cost an index vector could have, use the index vector.
-    uint32_t bit_vector_cost = BitVector::ApproxBytesCost(end_index_);
+    uint32_t bit_vector_cost = BitVector::ApproxBytesCost(r.end);
     uint32_t index_vector_cost_ub = sizeof(uint32_t) * count;
 
     // If either of the conditions hold which make it better to use an
@@ -527,7 +475,7 @@
         optimize_for_ == OptimizeFor::kLookupSpeed) {
       // Try and strike a good balance between not making the vector too
       // big and good performance.
-      std::vector<uint32_t> iv(std::min(kSmallRangeLimit, count));
+      IndexVector iv(std::min(kSmallRangeLimit, count));
 
       uint32_t out_i = 0;
       for (uint32_t i = 0; i < count; ++i) {
@@ -537,8 +485,8 @@
 
         // We keep this branch free by always writing the index but only
         // incrementing the out index if the return value is true.
-        bool value = p(i + start_index_);
-        iv[out_i] = i + start_index_;
+        bool value = p(i + r.start);
+        iv[out_i] = i + r.start;
         out_i += value;
       }
 
@@ -546,58 +494,44 @@
       iv.resize(out_i);
       iv.shrink_to_fit();
 
-      *this = RowMap(std::move(iv));
-      return;
+      return std::move(iv);
     }
 
     // Otherwise, create a bitvector which spans the full range using
     // |p| as the filler for the bits between start and end.
-    *this = RowMap(BitVector::Range(start_index_, end_index_, p));
+    return BitVector::Range(r.start, r.end, p);
   }
 
-  void InsertIntoBitVector(uint32_t row) {
-    PERFETTO_DCHECK(mode_ == Mode::kBitVector);
-
-    // If we're adding a row to precisely the end of the BitVector, just append
-    // true instead of resizing and then setting.
-    if (row == bit_vector_.size()) {
-      bit_vector_.AppendTrue();
-      return;
-    }
-
-    if (row > bit_vector_.size()) {
-      bit_vector_.Resize(row + 1, false);
-    }
-    bit_vector_.Set(row);
+  PERFETTO_ALWAYS_INLINE static OutputIndex GetRange(Range r, InputRow row) {
+    return r.start + row;
   }
-
-  PERFETTO_ALWAYS_INLINE OutputIndex GetRange(InputRow row) const {
-    PERFETTO_DCHECK(mode_ == Mode::kRange);
-    return start_index_ + row;
+  PERFETTO_ALWAYS_INLINE static OutputIndex GetBitVector(const BitVector& bv,
+                                                         uint32_t row) {
+    return bv.IndexOfNthSet(row);
   }
-  PERFETTO_ALWAYS_INLINE OutputIndex GetBitVector(uint32_t row) const {
-    PERFETTO_DCHECK(mode_ == Mode::kBitVector);
-    return bit_vector_.IndexOfNthSet(row);
-  }
-  PERFETTO_ALWAYS_INLINE OutputIndex GetIndexVector(uint32_t row) const {
-    PERFETTO_DCHECK(mode_ == Mode::kIndexVector);
-    return index_vector_[row];
+  PERFETTO_ALWAYS_INLINE static OutputIndex GetIndexVector(
+      const IndexVector& vec,
+      uint32_t row) {
+    return vec[row];
   }
 
   RowMap SelectRowsSlow(const RowMap& selector) const;
 
-  Mode mode_ = Mode::kRange;
+  static void InsertIntoBitVector(BitVector& bv, OutputIndex row) {
+    if (row == bv.size()) {
+      bv.AppendTrue();
+      return;
+    }
+    if (row > bv.size())
+      bv.Resize(row + 1, false);
+    bv.Set(row);
+  }
 
-  // Only valid when |mode_| == Mode::kRange.
-  OutputIndex start_index_ = 0;  // This is an inclusive index.
-  OutputIndex end_index_ = 0;    // This is an exclusive index.
+  PERFETTO_NORETURN void NoVariantMatched() const {
+    PERFETTO_FATAL("Didn't match any variant type.");
+  }
 
-  // Only valid when |mode_| == Mode::kBitVector.
-  BitVector bit_vector_;
-
-  // Only valid when |mode_| == Mode::kIndexVector.
-  std::vector<OutputIndex> index_vector_;
-
+  Variant data_;
   OptimizeFor optimize_for_ = OptimizeFor::kMemory;
 };
 
diff --git a/src/trace_processor/containers/row_map_unittest.cc b/src/trace_processor/containers/row_map_unittest.cc
index 6e1ca11..7f919bd 100644
--- a/src/trace_processor/containers/row_map_unittest.cc
+++ b/src/trace_processor/containers/row_map_unittest.cc
@@ -25,6 +25,191 @@
 namespace trace_processor {
 namespace {
 
+TEST(RowMapUnittest, SingleRow) {
+  RowMap rm(10, 20);
+  RowMap rm_row = rm.SingleRow(15u);
+  ASSERT_EQ(rm_row.size(), 1u);
+  ASSERT_TRUE(rm_row.Contains(15));
+  ASSERT_FALSE(rm_row.Contains(11));
+}
+
+TEST(RowMapUnittest, CopyRange) {
+  RowMap rm(10, 20);
+  RowMap rm_copy = rm.Copy();
+  ASSERT_EQ(rm_copy.size(), 10u);
+}
+
+TEST(RowMapUnittest, CopyBitVector) {
+  RowMap rm(BitVector{true, false, false, false, true, true});
+  RowMap rm_copy = rm.Copy();
+  ASSERT_EQ(rm_copy.size(), 3u);
+}
+
+TEST(RowMapUnittest, CopyIndexVector) {
+  RowMap rm(std::vector<uint32_t>{10, 17, 20, 21});
+  RowMap rm_copy = rm.Copy();
+  ASSERT_EQ(rm_copy.size(), 4u);
+}
+
+TEST(RowMapUnittest, GetFromRange) {
+  RowMap rm(10, 20);
+  ASSERT_EQ(rm.Get(5), 15u);
+}
+
+TEST(RowMapUnittest, GetFromBitVector) {
+  RowMap rm(BitVector{true, false, false, false, true, true});
+  ASSERT_EQ(rm.Get(1), 4u);
+}
+
+TEST(RowMapUnittest, GetFromIndexVector) {
+  RowMap rm(std::vector<uint32_t>{10, 17, 20, 21});
+  ASSERT_EQ(rm.Get(1), 17u);
+}
+
+TEST(RowMapUnittest, ContainsFromRange) {
+  RowMap rm(10, 20);
+  ASSERT_FALSE(rm.Contains(5));
+  ASSERT_TRUE(rm.Contains(15));
+}
+
+TEST(RowMapUnittest, ContainsFromBitVector) {
+  RowMap rm(BitVector{true, false, false, false, true, true});
+  ASSERT_FALSE(rm.Contains(3));
+  ASSERT_TRUE(rm.Contains(5));
+}
+
+TEST(RowMapUnittest, ContainsFromIndexVector) {
+  RowMap rm(std::vector<uint32_t>{10, 17, 20, 21});
+  ASSERT_FALSE(rm.Contains(5));
+  ASSERT_TRUE(rm.Contains(10));
+}
+
+TEST(RowMapUnittest, RowOfRange) {
+  RowMap rm(10, 20);
+  ASSERT_EQ(rm.RowOf(15).value(), 5u);
+  ASSERT_EQ(rm.RowOf(5), std::nullopt);
+}
+
+TEST(RowMapUnittest, RowOfBitVector) {
+  RowMap rm(BitVector{true, false, false, false, true, true});
+  ASSERT_EQ(rm.RowOf(4), 1u);
+  ASSERT_EQ(rm.RowOf(1), std::nullopt);
+}
+
+TEST(RowMapUnittest, RowOfIndexVector) {
+  RowMap rm(std::vector<uint32_t>{10, 17, 20, 21});
+  ASSERT_EQ(rm.RowOf(17), 1u);
+  ASSERT_EQ(rm.RowOf(5), std::nullopt);
+}
+
+TEST(RowMapUnittest, InsertIntoRangeAtTheEnd) {
+  RowMap rm(10, 20);
+  rm.Insert(21);
+  ASSERT_EQ(rm.size(), 11u);
+  ASSERT_TRUE(rm.Contains(21));
+}
+
+TEST(RowMapUnittest, InsertIntoRange) {
+  RowMap rm(10, 20);
+  rm.Insert(25);
+  ASSERT_EQ(rm.size(), 11u);
+  ASSERT_TRUE(rm.Contains(25));
+}
+
+TEST(RowMapUnittest, InsertIntoBitVector) {
+  RowMap rm(BitVector{true, false, false, false, true, true});
+  rm.Insert(25);
+  ASSERT_EQ(rm.size(), 4u);
+  ASSERT_TRUE(rm.Contains(25));
+}
+
+TEST(RowMapUnittest, InsertIntoIndexVector) {
+  RowMap rm(std::vector<uint32_t>{10, 17, 20, 21});
+  rm.Insert(25);
+  ASSERT_EQ(rm.size(), 5u);
+  ASSERT_TRUE(rm.Contains(25));
+}
+
+TEST(RowMapUnittest, SelectRowsFromRangeWithRange) {
+  RowMap rm(10, 20);
+
+  RowMap selector(4, 8);
+  RowMap selected = rm.SelectRows(selector);
+  ASSERT_EQ(selected.size(), 4u);
+  ASSERT_EQ(selected.Get(0), 14u);
+}
+
+TEST(RowMapUnittest, SelectRowsFromRangeWithBV) {
+  RowMap rm(10, 20);
+  // BitVector with values at 16, 18, 20 and so on.
+  RowMap selector(
+      BitVector::Range(4, 8, [](uint32_t x) { return x % 2 == 0; }));
+  RowMap selected = rm.SelectRows(selector);
+  ASSERT_EQ(selected.size(), 2u);
+  ASSERT_EQ(selected.Get(0), 14u);
+}
+
+TEST(RowMapUnittest, SelectRowsFromRangeWithIV) {
+  RowMap rm(10, 20);
+  RowMap selector(std::vector<uint32_t>{4, 6});
+  RowMap selected = rm.SelectRows(selector);
+  ASSERT_EQ(selected.size(), 2u);
+  ASSERT_EQ(selected.Get(0), 14u);
+}
+
+TEST(RowMapUnittest, SelectRowsFromBVWithRange) {
+  RowMap rm(BitVector::Range(10, 50, [](uint32_t x) { return x % 2 == 0; }));
+
+  RowMap selector(4, 8);
+  RowMap selected = rm.SelectRows(selector);
+  ASSERT_EQ(selected.size(), 4u);
+  ASSERT_EQ(selected.Get(0), 18u);
+}
+
+TEST(RowMapUnittest, SelectRowsFromBVWithBV) {
+  RowMap rm(BitVector::Range(10, 50, [](uint32_t x) { return x % 2 == 0; }));
+  // BitVector with values at 16, 18, 20 and so on.
+  RowMap selector(
+      BitVector::Range(4, 8, [](uint32_t x) { return x % 2 == 0; }));
+  RowMap selected = rm.SelectRows(selector);
+  ASSERT_EQ(selected.size(), 2u);
+  ASSERT_EQ(selected.Get(0), 18u);
+}
+
+TEST(RowMapUnittest, SelectRowsFromBVWithIV) {
+  RowMap rm(BitVector::Range(10, 50, [](uint32_t x) { return x % 2 == 0; }));
+  RowMap selector(std::vector<uint32_t>{4, 6});
+  RowMap selected = rm.SelectRows(selector);
+  ASSERT_EQ(selected.size(), 2u);
+  ASSERT_EQ(selected.Get(0), 18u);
+}
+
+TEST(RowMapUnittest, SelectRowsFromIVWithRange) {
+  RowMap rm(std::vector<uint32_t>{10, 12, 14, 16, 18, 20, 22, 24});
+
+  RowMap selector(4, 8);
+  RowMap selected = rm.SelectRows(selector);
+  ASSERT_EQ(selected.size(), 4u);
+  ASSERT_EQ(selected.Get(0), 18u);
+}
+
+TEST(RowMapUnittest, SelectRowsFromIVWithBV) {
+  RowMap rm(std::vector<uint32_t>{10, 12, 14, 16, 18, 20, 22, 24});
+  RowMap selector(
+      BitVector::Range(4, 8, [](uint32_t x) { return x % 2 == 0; }));
+  RowMap selected = rm.SelectRows(selector);
+  ASSERT_EQ(selected.size(), 2u);
+  ASSERT_EQ(selected.Get(0), 18u);
+}
+
+TEST(RowMapUnittest, SelectRowsFromIVWithIV) {
+  RowMap rm(std::vector<uint32_t>{10, 12, 14, 16, 18, 20, 22, 24});
+  RowMap selector(std::vector<uint32_t>{4, 6});
+  RowMap selected = rm.SelectRows(selector);
+  ASSERT_EQ(selected.size(), 2u);
+  ASSERT_EQ(selected.Get(0), 18u);
+}
+
 TEST(RowMapUnittest, SmokeRange) {
   RowMap rm(30, 47);
 
@@ -327,22 +512,86 @@
   ASSERT_EQ(rm.size(), 0u);
 }
 
-TEST(RowMapUnittest, IntersectManyRange) {
+TEST(RowMapUnittest, IntersectRangeWithRange) {
   RowMap rm(3, 7);
-  rm.Intersect(2, 4);
+  RowMap sec(2, 4);
+  rm.Intersect(sec);
 
   ASSERT_EQ(rm.size(), 1u);
   ASSERT_EQ(rm.Get(0u), 3u);
 }
 
-TEST(RowMapUnittest, IntersectManyIv) {
-  RowMap rm(std::vector<uint32_t>{3u, 2u, 0u, 1u, 1u, 3u});
-  rm.Intersect(2, 4);
+TEST(RowMapUnittest, IntersectRangeWithBV) {
+  RowMap rm(2, 4);
+  RowMap sec(BitVector{true, false, true, true, false, true});
+  rm.Intersect(sec);
+
+  ASSERT_EQ(rm.size(), 2u);
+  ASSERT_EQ(rm.Get(0), 2u);
+}
+
+TEST(RowMapUnittest, IntersectRangeWithIV) {
+  RowMap rm(2, 10);
+  RowMap sec(std::vector<uint32_t>{0, 2, 5});
+  rm.Intersect(sec);
+
+  ASSERT_EQ(rm.size(), 2u);
+  ASSERT_EQ(rm.Get(0u), 2u);
+}
+
+TEST(RowMapUnittest, IntersectBVWithRange) {
+  RowMap rm(BitVector{true, false, true, true, false, true});
+  RowMap sec(2, 4);
+  rm.Intersect(sec);
+
+  ASSERT_EQ(rm.size(), 2u);
+  ASSERT_EQ(rm.Get(0), 2u);
+}
+
+TEST(RowMapUnittest, IntersectBVWithBV) {
+  RowMap rm(BitVector{true, false, true, true, false, true});
+  RowMap sec(BitVector{false, true, true, false, false, true, true});
+  rm.Intersect(sec);
+
+  ASSERT_EQ(rm.size(), 2u);
+  ASSERT_EQ(rm.Get(0), 2u);
+}
+
+TEST(RowMapUnittest, IntersectBVWithIV) {
+  RowMap rm(BitVector{true, false, true, true, false, true});
+  RowMap sec(std::vector<uint32_t>{0, 2, 5});
+  rm.Intersect(sec);
 
   ASSERT_EQ(rm.size(), 3u);
-  ASSERT_EQ(rm.Get(0u), 3u);
-  ASSERT_EQ(rm.Get(1u), 2u);
-  ASSERT_EQ(rm.Get(2u), 3u);
+  ASSERT_EQ(rm.Get(0), 0u);
+}
+
+TEST(RowMapUnittest, IntersectIVWithRange) {
+  RowMap rm(std::vector<uint32_t>{0, 2, 5});
+  RowMap sec(2, 10);
+  rm.Intersect(sec);
+
+  ASSERT_EQ(rm.size(), 2u);
+  ASSERT_EQ(rm.Get(0u), 2u);
+}
+
+TEST(RowMapUnittest, IntersectIVWithBV) {
+  RowMap rm(std::vector<uint32_t>{0, 2, 5});
+  RowMap sec(BitVector{true, false, true, true, false, true});
+  rm.Intersect(sec);
+
+  ASSERT_EQ(rm.size(), 3u);
+  ASSERT_EQ(rm.Get(0), 0u);
+}
+
+TEST(RowMapUnittest, IntersectIVWithIV) {
+  RowMap rm(std::vector<uint32_t>{0, 2, 5});
+  RowMap sec(std::vector<uint32_t>{1, 2, 6});
+
+  rm.Intersect(sec);
+
+  ASSERT_EQ(rm.size(), 1u);
+  ASSERT_EQ(rm.Get(0u), 2u);
 }
 
 }  // namespace
diff --git a/src/trace_processor/containers/string_pool.cc b/src/trace_processor/containers/string_pool.cc
index ab0b42a..3dacca2 100644
--- a/src/trace_processor/containers/string_pool.cc
+++ b/src/trace_processor/containers/string_pool.cc
@@ -25,23 +25,6 @@
 namespace perfetto {
 namespace trace_processor {
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-// static
-constexpr size_t StringPool::kNumBlockIndexBits;
-// static
-constexpr size_t StringPool::kNumBlockOffsetBits;
-// static
-constexpr size_t StringPool::kLargeStringFlagBitMask;
-// static
-constexpr size_t StringPool::kBlockOffsetBitMask;
-// static
-constexpr size_t StringPool::kBlockIndexBitMask;
-// static
-constexpr size_t StringPool::kBlockSizeBytes;
-// static
-constexpr size_t StringPool::kMinLargeStringSizeBytes;
-#endif
-
 StringPool::StringPool() {
   static_assert(
       StringPool::kMinLargeStringSizeBytes <= StringPool::kBlockSizeBytes + 1,
diff --git a/src/trace_processor/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index bfe5e8a..043b2af 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("../../../gn/perfetto_tp_tables.gni")
 import("../../../gn/test.gni")
 
 source_set("db") {
@@ -19,10 +20,22 @@
     "base_id.h",
     "column.cc",
     "column.h",
+    "column_overlay.cc",
+    "column_overlay.h",
     "column_storage.cc",
     "column_storage.h",
     "column_storage_overlay.h",
     "compare.h",
+    "null_overlay.cc",
+    "null_overlay.h",
+    "numeric_storage.cc",
+    "numeric_storage.h",
+    "sorting_overlay.h",
+    "storage.cc",
+    "storage.h",
+    "storage_overlay.cc",
+    "storage_overlay.h",
+    "storage_variants.h",
     "table.cc",
     "table.h",
     "typed_column.h",
@@ -40,16 +53,21 @@
   ]
 }
 
+perfetto_tp_tables("view_unittest") {
+  sources = [ "view_unittest.py" ]
+}
+
 perfetto_unittest_source_set("unittests") {
   testonly = true
   sources = [
     "column_storage_overlay_unittest.cc",
     "compare_unittest.cc",
-    "table_unittest.cc",
+    "storage_unittest.cc",
     "view_unittest.cc",
   ]
   deps = [
     ":db",
+    ":view_unittest",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
     "../../base",
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 04425b9..f1adb96 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -367,6 +367,9 @@
   // Returns the type of this Column in terms of SqlValue::Type.
   SqlValue::Type type() const { return ToSqlValueType(type_); }
 
+  // Returns the type of this Column in terms of ColumnType.
+  ColumnType col_type() const { return type_; }
+
   // Test the type of this Column.
   template <typename T>
   bool IsColumnType() const {
@@ -382,6 +385,9 @@
   // Returns true if this column is a sorted column.
   bool IsSorted() const { return IsSorted(flags_); }
 
+  // Returns true if this column is a dense column.
+  bool IsDense() const { return IsDense(flags_); }
+
   // Returns true if this column is a set id column.
   // Public for testing.
   bool IsSetId() const { return IsSetId(flags_); }
@@ -439,30 +445,27 @@
     return IsFlagsAndTypeValid(flags, ColumnTypeHelper<T>::ToColumnType());
   }
 
- protected:
   template <typename T>
   using stored_type = typename tc_internal::TypeHandler<T>::stored_type;
 
   // Returns the backing sparse vector cast to contain data of type T.
   // Should only be called when |type_| == ToColumnType<T>().
   template <typename T>
-  ColumnStorage<stored_type<T>>* mutable_storage() {
-    PERFETTO_DCHECK(ColumnTypeHelper<T>::ToColumnType() == type_);
-    PERFETTO_DCHECK(tc_internal::TypeHandler<T>::is_optional == IsNullable());
-    return static_cast<ColumnStorage<stored_type<T>>*>(storage_);
-  }
-
-  // Returns the backing sparse vector cast to contain data of type T.
-  // Should only be called when |type_| == ToColumnType<T>().
-  template <typename T>
   const ColumnStorage<stored_type<T>>& storage() const {
     PERFETTO_DCHECK(ColumnTypeHelper<T>::ToColumnType() == type_);
     PERFETTO_DCHECK(tc_internal::TypeHandler<T>::is_optional == IsNullable());
     return *static_cast<ColumnStorage<stored_type<T>>*>(storage_);
   }
 
-  // Returns true if this column is a dense column.
-  bool IsDense() const { return IsDense(flags_); }
+ protected:
+  // Returns the backing sparse vector cast to contain data of type T.
+  // Should only be called when |type_| == ToColumnType<T>().
+  template <typename T>
+  ColumnStorage<stored_type<T>>* mutable_storage() {
+    PERFETTO_DCHECK(ColumnTypeHelper<T>::ToColumnType() == type_);
+    PERFETTO_DCHECK(tc_internal::TypeHandler<T>::is_optional == IsNullable());
+    return static_cast<ColumnStorage<stored_type<T>>*>(storage_);
+  }
 
   // Returns true if this column is a hidden column.
   bool IsHidden() const { return (flags_ & Flag::kHidden) != 0; }
@@ -545,31 +548,31 @@
             b, std::lower_bound(b, e, value, &compare::SqlValueComparator));
         uint32_t end = std::distance(
             b, std::upper_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect(beg, end);
+        rm->Intersect({beg, end});
         return true;
       }
       case FilterOp::kLe: {
         uint32_t end = std::distance(
             b, std::upper_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect(0, end);
+        rm->Intersect({0, end});
         return true;
       }
       case FilterOp::kLt: {
         uint32_t end = std::distance(
             b, std::lower_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect(0, end);
+        rm->Intersect({0, end});
         return true;
       }
       case FilterOp::kGe: {
         uint32_t beg = std::distance(
             b, std::lower_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect(beg, overlay().size());
+        rm->Intersect({beg, overlay().size()});
         return true;
       }
       case FilterOp::kGt: {
         uint32_t beg = std::distance(
             b, std::upper_bound(b, e, value, &compare::SqlValueComparator));
-        rm->Intersect(beg, overlay().size());
+        rm->Intersect({beg, overlay().size()});
         return true;
       }
       case FilterOp::kNe:
@@ -608,11 +611,13 @@
     // Otherwise, find the end of the set and return the intersection for this.
     for (uint32_t i = set_id + 1; i < ov.size(); ++i) {
       if (st.Get(ov.Get(i)) != filter_set_id) {
-        rm->Intersect(set_id, i);
+        RowMap r(set_id, i);
+        rm->Intersect(r);
         return;
       }
     }
-    rm->Intersect(set_id, ov.size());
+    RowMap r(set_id, ov.size());
+    rm->Intersect(r);
   }
 
   // Slow path filter method which will perform a full table scan.
diff --git a/src/trace_processor/types/variadic.cc b/src/trace_processor/db/column_overlay.cc
similarity index 77%
rename from src/trace_processor/types/variadic.cc
rename to src/trace_processor/db/column_overlay.cc
index 9a26bd5..599a3b2 100644
--- a/src/trace_processor/types/variadic.cc
+++ b/src/trace_processor/db/column_overlay.cc
@@ -1,5 +1,5 @@
-#/*
- * Copyright (C) 2018 The Android Open Source Project
+/*
+ * 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.
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/db/column_overlay.h"
 
 namespace perfetto {
 namespace trace_processor {
+namespace column {
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-constexpr const char* Variadic::kTypeNames[];
-#endif
+ColumnOverlay::~ColumnOverlay() = default;
 
+}  // namespace column
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/column_overlay.h b/src/trace_processor/db/column_overlay.h
new file mode 100644
index 0000000..e3bad53
--- /dev/null
+++ b/src/trace_processor/db/column_overlay.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_DB_COLUMN_OVERLAY_H_
+#define SRC_TRACE_PROCESSOR_DB_COLUMN_OVERLAY_H_
+
+#include <variant>
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+// Column overlay introduce separation between column storage (vector of data)
+// and state (nullability, sorting) and actions (filtering, expanding, joining)
+// done on the storage. This is a composable design - one ColumnOverlay
+// subclass might hold another subclass, and each of them implements all of the
+// functions in it's own specific way.
+class ColumnOverlay {
+ public:
+  virtual ~ColumnOverlay();
+
+  // Clears the rows of RowMap, on which data don't match the FilterOp operation
+  // with SqlValue. Efficient.
+  virtual void Filter(FilterOp, SqlValue, RowMap&) const = 0;
+
+  // Sorts (ascending) provided vector of indices based on storage.
+  virtual void StableSort(uint32_t* rows, uint32_t rows_size) const = 0;
+};
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_COLUMN_OVERLAY_H_
diff --git a/src/trace_processor/db/column_storage.h b/src/trace_processor/db/column_storage.h
index 6cf2f30..7e6a609 100644
--- a/src/trace_processor/db/column_storage.h
+++ b/src/trace_processor/db/column_storage.h
@@ -53,6 +53,7 @@
   void Set(uint32_t idx, T val) { vector_[idx] = val; }
   uint32_t size() const { return static_cast<uint32_t>(vector_.size()); }
   void ShrinkToFit() { vector_.shrink_to_fit(); }
+  const std::vector<T>& vector() const { return vector_; }
 
   template <bool IsDense>
   static ColumnStorage<T> Create() {
@@ -83,6 +84,14 @@
   uint32_t size() const { return nv_.size(); }
   bool IsDense() const { return nv_.IsDense(); }
   void ShrinkToFit() { nv_.ShrinkToFit(); }
+  // For dense columns the size of the vector is equal to size of the bit
+  // vector. For sparse it's equal to count set bits of the bit vector.
+  const std::vector<T>& non_null_vector() const {
+    return nv_.non_null_vector();
+  }
+  const BitVector& non_null_bit_vector() const {
+    return nv_.non_null_bit_vector();
+  }
 
   template <bool IsDense>
   static ColumnStorage<std::optional<T>> Create() {
diff --git a/src/trace_processor/db/column_storage_overlay.h b/src/trace_processor/db/column_storage_overlay.h
index 108dccd..997d4be 100644
--- a/src/trace_processor/db/column_storage_overlay.h
+++ b/src/trace_processor/db/column_storage_overlay.h
@@ -183,24 +183,12 @@
     // meet |p|. However, if |this| is a BitVector, we end up needing expensive
     // |IndexOfNthSet| calls (as we need to convert the row to an index before
     // passing it to |p|).
-    switch (row_map_.mode_) {
-      case RowMap::Mode::kRange: {
-        auto ip = [this, p](uint32_t row) { return p(row_map_.GetRange(row)); };
-        out->Filter(ip);
-        break;
-      }
-      case RowMap::Mode::kBitVector: {
-        FilterIntoScanSelfBv(out, p);
-        break;
-      }
-      case RowMap::Mode::kIndexVector: {
-        auto ip = [this, p](uint32_t row) {
-          return p(row_map_.GetIndexVector(row));
-        };
-        out->Filter(ip);
-        break;
-      }
+    if (row_map_.IsBitVector()) {
+      FilterIntoScanSelfBv(out, p);
+      return;
     }
+    auto ip = [this, p](uint32_t row) { return p(row_map_.Get(row)); };
+    out->Filter(ip);
   }
 
   template <typename Comparator = bool(uint32_t, uint32_t)>
@@ -217,54 +205,57 @@
   // Filters the current ColumnStorageOverlay into |out| by performing a full
   // scan on |row_map.bit_vector_|. See |FilterInto| for a full breakdown of the
   // semantics of this function.
-  template <typename Predicate>
-  void FilterIntoScanSelfBv(RowMap* out, Predicate p) const {
-    auto it = row_map_.bit_vector_.IterateSetBits();
-    switch (out->mode_) {
-      case RowMap::Mode::kRange: {
-        // TODO(lalitm): investigate whether we can reuse the data inside
-        // out->bit_vector_ at some point.
-        BitVector bv(out->end_index_, false);
-        for (auto out_it = bv.IterateAllBits(); it; it.Next(), out_it.Next()) {
-          uint32_t ordinal = it.ordinal();
-          if (ordinal < out->start_index_)
-            continue;
-          if (ordinal >= out->end_index_)
-            break;
 
-          if (p(it.index())) {
-            out_it.Set();
-          }
+  template <typename Predicate>
+  struct FilterIntoScanSelfBvVisitor {
+    void operator()(RowMap::Range out_r) {
+      BitVector bv(out_r.end, false);
+      for (auto out_it = bv.IterateAllBits(); bv_iter;
+           bv_iter.Next(), out_it.Next()) {
+        uint32_t ordinal = bv_iter.ordinal();
+        if (ordinal < out_r.start)
+          continue;
+        if (ordinal >= out_r.end)
+          break;
+
+        if (p(bv_iter.index())) {
+          out_it.Set();
         }
-        *out = RowMap(std::move(bv));
-        break;
       }
-      case RowMap::Mode::kBitVector: {
-        auto out_it = out->bit_vector_.IterateAllBits();
-        for (; out_it; it.Next(), out_it.Next()) {
-          PERFETTO_DCHECK(it);
-          if (out_it.IsSet() && !p(it.index()))
-            out_it.Clear();
-        }
-        break;
-      }
-      case RowMap::Mode::kIndexVector: {
-        PERFETTO_DCHECK(std::is_sorted(out->index_vector_.begin(),
-                                       out->index_vector_.end()));
-        auto fn = [&p, &it](uint32_t i) {
-          while (it.ordinal() < i) {
-            it.Next();
-            PERFETTO_DCHECK(it);
-          }
-          PERFETTO_DCHECK(it.ordinal() == i);
-          return !p(it.index());
-        };
-        auto iv_it = std::remove_if(out->index_vector_.begin(),
-                                    out->index_vector_.end(), fn);
-        out->index_vector_.erase(iv_it, out->index_vector_.end());
-        break;
+      *out = RowMap(std::move(bv));
+    }
+    void operator()(const BitVector& out_bv) {
+      auto out_it = out_bv.IterateAllBits();
+      for (; out_it; bv_iter.Next(), out_it.Next()) {
+        PERFETTO_DCHECK(bv_iter);
+        if (out_it.IsSet() && !p(bv_iter.index()))
+          out_it.Clear();
       }
     }
+    void operator()(std::vector<OutputIndex>& out_vec) {
+      PERFETTO_DCHECK(std::is_sorted(out_vec.begin(), out_vec.end()));
+      auto fn = [this](uint32_t i) {
+        while (bv_iter.ordinal() < i) {
+          bv_iter.Next();
+          PERFETTO_DCHECK(bv_iter);
+        }
+        PERFETTO_DCHECK(bv_iter.ordinal() == i);
+        return !p(bv_iter.index());
+      };
+      auto iv_it = std::remove_if(out_vec.begin(), out_vec.end(), fn);
+      out_vec.erase(iv_it, out_vec.end());
+    }
+    RowMap* out;
+    Predicate p;
+    internal::SetBitsIterator bv_iter;
+  };
+
+  template <typename Predicate>
+  void FilterIntoScanSelfBv(RowMap* out, Predicate p) const {
+    const BitVector* bv = std::get_if<BitVector>(&row_map_.data_);
+    auto it = bv->IterateSetBits();
+    std::visit(FilterIntoScanSelfBvVisitor<Predicate>{out, p, std::move(it)},
+               out->data_);
   }
 
   RowMap row_map_;
diff --git a/src/trace_processor/db/null_overlay.cc b/src/trace_processor/db/null_overlay.cc
new file mode 100644
index 0000000..ade46c5
--- /dev/null
+++ b/src/trace_processor/db/null_overlay.cc
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/db/null_overlay.h"
+
+#include "src/trace_processor/db/storage_variants.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+void NullOverlay::Filter(FilterOp op, SqlValue sql_val, RowMap& rm) const {
+  if (op == FilterOp::kIsNull) {
+    rm.Intersect(RowMap(null_bv_->Not()));
+    return;
+  }
+  if (op == FilterOp::kIsNotNull) {
+    rm.Intersect(RowMap(null_bv_->Copy()));
+    return;
+  }
+
+  // Row map for filtered data, not the size of whole column.
+  RowMap filtered_data_rm(0, null_bv_->CountSetBits());
+  inner_->Filter(op, sql_val, filtered_data_rm);
+
+  // Select only rows that were not filtered out from null BitVector and
+  // intersect it with RowMap&.
+  rm.Intersect(RowMap(null_bv_->Copy()).SelectRows(filtered_data_rm));
+}
+
+void NullOverlay::StableSort(uint32_t* rows, uint32_t rows_size) const {
+  uint32_t count_set_bits = null_bv_->CountSetBits();
+
+  std::vector<uint32_t> non_null_rows(count_set_bits);
+  std::vector<uint32_t> storage_to_rows(count_set_bits);
+
+  // Saving the map from `out` index to `storage` index gives us free `IsSet()`
+  // function, which would be very expensive otherwise.
+  for (auto it = null_bv_->IterateSetBits(); it; it.Next()) {
+    storage_to_rows[it.ordinal()] = it.index();
+  }
+
+  uint32_t cur_non_null_id = 0;
+  uint32_t cur_null_id = 0;
+
+  // Sort elements into null and non null.
+  for (uint32_t i = 0; i < rows_size; ++i) {
+    uint32_t row_idx = rows[i];
+    auto it = std::lower_bound(storage_to_rows.begin(), storage_to_rows.end(),
+                               row_idx);
+
+    // This condition holds if the row is null.
+    if (it == storage_to_rows.end() || *it != row_idx) {
+      // We can override the out because we already used this data.
+      rows[cur_null_id++] = row_idx;
+      continue;
+    }
+
+    uint32_t non_null_idx =
+        static_cast<uint32_t>(std::distance(storage_to_rows.begin(), it));
+    non_null_rows[cur_non_null_id++] = non_null_idx;
+  }
+
+  // Sort storage and translate them into `rows` indices.
+  inner_->StableSort(non_null_rows.data(), count_set_bits);
+  uint32_t set_rows_offset = null_bv_->size() - count_set_bits;
+  for (uint32_t i = 0; i < count_set_bits; ++i) {
+    rows[set_rows_offset + i] = storage_to_rows[non_null_rows[i]];
+  }
+}
+
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/null_overlay.h b/src/trace_processor/db/null_overlay.h
new file mode 100644
index 0000000..43d2fed
--- /dev/null
+++ b/src/trace_processor/db/null_overlay.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_DB_NULL_OVERLAY_H_
+#define SRC_TRACE_PROCESSOR_DB_NULL_OVERLAY_H_
+
+#include <variant>
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/column_overlay.h"
+#include "src/trace_processor/db/storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+// Overlay responsible for operations related to column nullability.
+class NullOverlay : public ColumnOverlay {
+ public:
+  explicit NullOverlay(std::unique_ptr<ColumnOverlay> inner,
+                       const BitVector* null_bv)
+      : inner_(std::move(inner)), null_bv_(null_bv) {}
+
+  void Filter(FilterOp, SqlValue, RowMap&) const override;
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+ private:
+  std::unique_ptr<ColumnOverlay> inner_;
+
+  // Vector of data nullability.
+  const BitVector* null_bv_;
+};
+
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_NULL_OVERLAY_H_
diff --git a/src/trace_processor/db/numeric_storage.cc b/src/trace_processor/db/numeric_storage.cc
new file mode 100644
index 0000000..3b66e22
--- /dev/null
+++ b/src/trace_processor/db/numeric_storage.cc
@@ -0,0 +1,308 @@
+
+/*
+ * 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.
+ */
+
+#include <variant>
+
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/numeric_storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+namespace {
+
+// Templated part of FastPathComparison.
+template <typename T>
+inline void TypedFastPathComparison(std::optional<NumericValue> val,
+                                    FilterOp op,
+                                    const T* start,
+                                    uint32_t num_elements,
+                                    BitVector::Builder& builder) {
+  if (!val) {
+    builder.Skip(num_elements);
+    return;
+  }
+  std::visit(
+      [val, start, num_elements, &builder](auto comparator) {
+        T typed_val = std::get<T>(*val);
+        for (uint32_t i = 0; i < num_elements; i += BitVector::kBitsInWord) {
+          uint64_t word = 0;
+          // This part should be optimised by SIMD and is expected to be fast.
+          for (uint32_t k = 0; k < BitVector::kBitsInWord; ++k) {
+            bool comp_result = comparator(start[i + k], typed_val);
+            word |= static_cast<uint64_t>(comp_result) << k;
+          }
+          builder.AppendWord(word);
+        }
+      },
+      GetFilterOpVariant<T>(op));
+}
+
+// Templated part of SlowPathComparison.
+template <typename T>
+inline void TypedSlowPathComparison(std::optional<NumericValue> val,
+                                    FilterOp op,
+                                    const T* start,
+                                    uint32_t num_elements,
+                                    BitVector::Builder& builder) {
+  if (!val) {
+    builder.Skip(num_elements);
+    return;
+  }
+  std::visit(
+      [val, start, num_elements, &builder](auto comparator) {
+        T typed_val = std::get<T>(*val);
+        for (uint32_t i = 0; i < num_elements; ++i) {
+          builder.Append(comparator(start[i], typed_val));
+        }
+      },
+      GetFilterOpVariant<T>(op));
+}
+
+}  // namespace
+
+void NumericStorage::StableSort(uint32_t* rows, uint32_t rows_size) const {
+  NumericValue val = *GetNumericTypeVariant(type_, SqlValue::Long(0));
+  std::visit(
+      [this, &rows, rows_size](auto val_data) {
+        using T = decltype(val_data);
+        const T* typed_start = static_cast<const T*>(data_);
+        std::stable_sort(rows, rows + rows_size,
+                         [typed_start](uint32_t a_idx, uint32_t b_idx) {
+                           T first_val = typed_start[a_idx];
+                           T second_val = typed_start[b_idx];
+                           return first_val < second_val;
+                         });
+      },
+      val);
+}
+
+// Responsible for invoking templated version of FastPathComparison.
+void NumericStorage::CompareFast(FilterOp op,
+                                 SqlValue sql_val,
+                                 uint32_t offset,
+                                 uint32_t num_elements,
+                                 BitVector::Builder& builder) const {
+  PERFETTO_DCHECK(num_elements % BitVector::kBitsInWord == 0);
+  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+
+  // If the value is invalid we should just ignore those elements.
+  if (!val.has_value() || op == FilterOp::kIsNotNull ||
+      op == FilterOp::kIsNull || op == FilterOp::kGlob) {
+    builder.Skip(num_elements);
+    return;
+  }
+  std::visit(
+      [this, op, offset, num_elements, &builder](auto num_val) {
+        using T = decltype(num_val);
+        auto* typed_start = static_cast<const T*>(data_) + offset;
+        TypedFastPathComparison(num_val, op, typed_start, num_elements,
+                                builder);
+      },
+      *val);
+}
+
+// Responsible for invoking templated version of SlowPathComparison.
+void NumericStorage::CompareSlow(FilterOp op,
+                                 SqlValue sql_val,
+                                 uint32_t offset,
+                                 uint32_t num_elements,
+                                 BitVector::Builder& builder) const {
+  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+
+  // If the value is invalid we should just ignore those elements.
+  if (!val.has_value() || op == FilterOp::kIsNotNull ||
+      op == FilterOp::kIsNull || op == FilterOp::kGlob) {
+    builder.Skip(num_elements);
+    return;
+  }
+
+  std::visit(
+      [this, op, offset, num_elements, &builder](auto val) {
+        using T = decltype(val);
+        auto* typed_start = static_cast<const T*>(data_) + offset;
+        TypedSlowPathComparison(val, op, typed_start, num_elements, builder);
+      },
+      *val);
+}
+
+uint32_t NumericStorage::UpperBoundIndex(NumericValue val) const {
+  return std::visit(
+      [this](auto val_data) {
+        using T = decltype(val_data);
+        const T* typed_start = static_cast<const T*>(data_);
+        auto upper =
+            std::upper_bound(typed_start, typed_start + size_, val_data);
+        return static_cast<uint32_t>(std::distance(typed_start, upper));
+      },
+      val);
+}
+
+// As we don't template those functions, we need to use std::visitor to type
+// `start`, hence this wrapping.
+uint32_t NumericStorage::LowerBoundIndex(NumericValue val) const {
+  return std::visit(
+      [this](auto val_data) {
+        using T = decltype(val_data);
+        const T* typed_start = static_cast<const T*>(data_);
+        auto lower =
+            std::lower_bound(typed_start, typed_start + size_, val_data);
+        return static_cast<uint32_t>(std::distance(typed_start, lower));
+      },
+      val);
+}
+
+void NumericStorage::CompareSorted(FilterOp op,
+                                   SqlValue sql_val,
+                                   RowMap& rm) const {
+  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+  if (!val.has_value() || op == FilterOp::kIsNotNull ||
+      op == FilterOp::kIsNull || op == FilterOp::kGlob) {
+    rm.Clear();
+    return;
+  }
+
+  switch (op) {
+    case FilterOp::kEq: {
+      uint32_t beg = LowerBoundIndex(*val);
+      uint32_t end = UpperBoundIndex(*val);
+      RowMap sec(beg, end);
+      rm.Intersect(sec);
+      return;
+    }
+    case FilterOp::kLe: {
+      uint32_t end = UpperBoundIndex(*val);
+      RowMap sec(0, end);
+      rm.Intersect(sec);
+      return;
+    }
+    case FilterOp::kLt: {
+      uint32_t end = LowerBoundIndex(*val);
+      RowMap sec(0, end);
+      rm.Intersect(sec);
+      return;
+    }
+    case FilterOp::kGe: {
+      uint32_t beg = LowerBoundIndex(*val);
+      RowMap sec(beg, size_);
+      rm.Intersect(sec);
+      return;
+    }
+    case FilterOp::kGt: {
+      uint32_t beg = UpperBoundIndex(*val);
+      RowMap sec(beg, size_);
+      rm.Intersect(sec);
+      return;
+    }
+    case FilterOp::kNe:
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+    case FilterOp::kGlob:
+      rm.Clear();
+  }
+  return;
+}
+
+uint32_t NumericStorage::UpperBoundIndex(NumericValue val,
+                                         uint32_t* order) const {
+  return std::visit(
+      [this, order](auto val_data) {
+        using T = decltype(val_data);
+        const T* typed_start = static_cast<const T*>(data_);
+        auto upper = std::upper_bound(order, order + size_, val_data,
+                                      [typed_start](T val, uint32_t index) {
+                                        return val < *(typed_start + index);
+                                      });
+        return static_cast<uint32_t>(std::distance(order, upper));
+      },
+      val);
+}
+
+// As we don't template those functions, we need to use std::visitor to type
+// `start`, hence this wrapping.
+uint32_t NumericStorage::LowerBoundIndex(NumericValue val,
+                                         uint32_t* order) const {
+  return std::visit(
+      [this, order](auto val_data) {
+        using T = decltype(val_data);
+        const T* typed_start = static_cast<const T*>(data_);
+        auto lower = std::lower_bound(order, order + size_, val_data,
+                                      [typed_start](uint32_t index, T val) {
+                                        return *(typed_start + index) < val;
+                                      });
+        return static_cast<uint32_t>(std::distance(order, lower));
+      },
+      val);
+}
+
+void NumericStorage::CompareSortedIndexes(FilterOp op,
+                                          SqlValue sql_val,
+                                          uint32_t* order,
+                                          RowMap& rm) const {
+  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+  if (!val.has_value() || op == FilterOp::kIsNotNull ||
+      op == FilterOp::kIsNull || op == FilterOp::kGlob) {
+    rm.Clear();
+    return;
+  }
+
+  switch (op) {
+    case FilterOp::kEq: {
+      uint32_t beg = LowerBoundIndex(*val, order);
+      uint32_t end = UpperBoundIndex(*val, order);
+      std::vector<uint32_t> index(order + beg, order + end);
+      rm.Intersect(RowMap(std::move(index)));
+      return;
+    }
+    case FilterOp::kLe: {
+      uint32_t end = UpperBoundIndex(*val, order);
+      std::vector<uint32_t> index(order, order + end);
+      rm.Intersect(RowMap(std::move(index)));
+      return;
+    }
+    case FilterOp::kLt: {
+      uint32_t end = LowerBoundIndex(*val, order);
+      std::vector<uint32_t> index(order, order + end);
+      rm.Intersect(RowMap(std::move(index)));
+      return;
+    }
+    case FilterOp::kGe: {
+      uint32_t beg = LowerBoundIndex(*val, order);
+      std::vector<uint32_t> index(order + beg, order + size_);
+      rm.Intersect(RowMap(std::move(index)));
+      return;
+    }
+    case FilterOp::kGt: {
+      uint32_t beg = UpperBoundIndex(*val, order);
+      std::vector<uint32_t> index(order + beg, order + size_);
+      rm.Intersect(RowMap(std::move(index)));
+      return;
+    }
+    case FilterOp::kNe:
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+    case FilterOp::kGlob:
+      rm.Clear();
+  }
+  return;
+}
+
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/numeric_storage.h b/src/trace_processor/db/numeric_storage.h
new file mode 100644
index 0000000..a85044b
--- /dev/null
+++ b/src/trace_processor/db/numeric_storage.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef SRC_TRACE_PROCESSOR_DB_NUMERIC_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_NUMERIC_STORAGE_H_
+
+#include <variant>
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/storage.h"
+#include "src/trace_processor/db/storage_variants.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+class NumericStorage : public Storage {
+ public:
+  NumericStorage(void* data, uint32_t size, ColumnType type)
+      : type_(type), data_(data), size_(size) {}
+
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+  void CompareFast(FilterOp op,
+                   SqlValue val,
+                   uint32_t offset,
+                   uint32_t num_elements,
+                   BitVector::Builder& builder) const override;
+
+  void CompareSlow(FilterOp op,
+                   SqlValue val,
+                   uint32_t offset,
+                   uint32_t num_elements,
+                   BitVector::Builder& builder) const override;
+
+  void CompareSorted(FilterOp op, SqlValue val, RowMap&) const override;
+
+  void CompareSortedIndexes(FilterOp op,
+                            SqlValue val,
+                            uint32_t* order,
+                            RowMap&) const override;
+
+  uint32_t size() const override { return size_; }
+
+ private:
+  // As we don't template those functions, we need to use std::visitor to type
+  // `start`, hence this wrapping.
+  uint32_t UpperBoundIndex(NumericValue val) const;
+
+  // As we don't template those functions, we need to use std::visitor to type
+  // `start`, hence this wrapping.
+  uint32_t LowerBoundIndex(NumericValue val) const;
+
+  // As we don't template those functions, we need to use std::visitor to type
+  // `start`, hence this wrapping.
+  uint32_t UpperBoundIndex(NumericValue val, uint32_t* order) const;
+
+  // As we don't template those functions, we need to use std::visitor to type
+  // `start`, hence this wrapping.
+  uint32_t LowerBoundIndex(NumericValue val, uint32_t* order) const;
+
+  const ColumnType type_;
+  const void* data_;
+  const uint32_t size_;
+};
+
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
+#endif  // SRC_TRACE_PROCESSOR_DB_NUMERIC_STORAGE_H_
diff --git a/src/trace_processor/db/sorting_overlay.h b/src/trace_processor/db/sorting_overlay.h
new file mode 100644
index 0000000..7b536a1
--- /dev/null
+++ b/src/trace_processor/db/sorting_overlay.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_DB_SORTING_OVERLAY_H_
+#define SRC_TRACE_PROCESSOR_DB_SORTING_OVERLAY_H_
+
+#include <variant>
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/column_overlay.h"
+#include "src/trace_processor/db/storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+// Overlay responsible for operations related to column sorted state.
+class SortingOverlay : public ColumnOverlay {
+ public:
+  explicit SortingOverlay(ColumnOverlay* ancestor);
+  void Filter(FilterOp, SqlValue, RowMap&) const override;
+  void StableSort(uint32_t* rows_order, uint32_t rows_size) const override;
+
+ private:
+  std::unique_ptr<ColumnOverlay> inner_;
+
+  // Index vector of data sorted in ascending order.
+  const std::vector<uint32_t>* sorted_state_;
+};
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_SORTING_OVERLAY_H_
diff --git a/src/trace_processor/types/variadic.cc b/src/trace_processor/db/storage.cc
similarity index 77%
copy from src/trace_processor/types/variadic.cc
copy to src/trace_processor/db/storage.cc
index 9a26bd5..4799d04 100644
--- a/src/trace_processor/types/variadic.cc
+++ b/src/trace_processor/db/storage.cc
@@ -1,5 +1,5 @@
-#/*
- * Copyright (C) 2018 The Android Open Source Project
+/*
+ * 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.
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/db/storage.h"
 
 namespace perfetto {
 namespace trace_processor {
+namespace column {
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-constexpr const char* Variadic::kTypeNames[];
-#endif
+Storage::~Storage() = default;
 
+}  // namespace column
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/storage.h b/src/trace_processor/db/storage.h
new file mode 100644
index 0000000..41e616f
--- /dev/null
+++ b/src/trace_processor/db/storage.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_H_
+
+#include <variant>
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/db/column.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+// Most base column interpreting layer - responsible for implementing operations
+// that require looking at the data, such as comparison or sorting.
+class Storage {
+ public:
+  virtual ~Storage();
+
+  // Changes the vector of indices to represent the sorted state of the column.
+  virtual void StableSort(uint32_t* rows, uint32_t rows_size) const = 0;
+
+  // Efficiently compares series of |num_elements| of data from |data_start| to
+  // comparator value and appends results to BitVector::Builder. Should be used
+  // on as much data as possible.
+  virtual void CompareFast(FilterOp op,
+                           SqlValue value,
+                           uint32_t offset,
+                           uint32_t compare_elements_count,
+                           BitVector::Builder&) const = 0;
+
+  // Inefficiently compares series of |num_elements| of data from |data_start|
+  // to comparator value and appends results to BitVector::Builder. Should be
+  // avoided if possible, with `FastSeriesComparison` used instead.
+  virtual void CompareSlow(FilterOp op,
+                           SqlValue value,
+                           uint32_t offset,
+                           uint32_t compare_elements_count,
+                           BitVector::Builder&) const = 0;
+
+  // Compares sorted (asc) series data with comparator value. Should be used
+  // where possible.
+  virtual void CompareSorted(FilterOp op, SqlValue value, RowMap&) const = 0;
+
+  // Compares sorted (asc) with `order` vector series with comparator value.
+  // Should be used where possible.
+  virtual void CompareSortedIndexes(FilterOp op,
+                                    SqlValue value,
+                                    uint32_t* order,
+                                    RowMap&) const = 0;
+
+  // Number of elements in stored data.
+  virtual uint32_t size() const = 0;
+};
+
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_H_
diff --git a/src/trace_processor/db/storage_overlay.cc b/src/trace_processor/db/storage_overlay.cc
new file mode 100644
index 0000000..2ab4fde
--- /dev/null
+++ b/src/trace_processor/db/storage_overlay.cc
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/db/storage_overlay.h"
+
+#include "src/trace_processor/db/storage_variants.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+void StorageOverlay::Filter(FilterOp op, SqlValue value, RowMap& rm) const {
+  if (op == FilterOp::kIsNotNull)
+    return;
+
+  if (op == FilterOp::kIsNull) {
+    rm.Clear();
+    return;
+  }
+
+  BitVector::Builder builder(storage_->size());
+  // Slow path: we compare <64 elements and append to get us to a word
+  // boundary.
+  uint32_t front_elements = builder.BitsUntilWordBoundaryOrFull();
+  storage_->CompareSlow(op, value, 0, front_elements, builder);
+  uint32_t cur_index = front_elements;
+
+  // Fast path: we compare as many groups of 64 elements as we can.
+  // This should be very easy for the compiler to auto-vectorize.
+  uint32_t fast_path_elements = builder.BitsInCompleteWordsUntilFull();
+  storage_->CompareFast(op, value, cur_index, fast_path_elements, builder);
+  cur_index += fast_path_elements;
+
+  // Slow path: we compare <64 elements and append to fill the Builder.
+  uint32_t back_elements = builder.BitsUntilFull();
+  storage_->CompareSlow(op, value, cur_index, back_elements, builder);
+
+  BitVector bv = std::move(builder).Build();
+  rm.Intersect(RowMap(std::move(bv)));
+}
+
+void StorageOverlay::StableSort(uint32_t* rows, uint32_t rows_size) const {
+  storage_->StableSort(rows, rows_size);
+}
+
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage_overlay.h b/src/trace_processor/db/storage_overlay.h
new file mode 100644
index 0000000..9b0b5ec
--- /dev/null
+++ b/src/trace_processor/db/storage_overlay.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_OVERLAY_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_OVERLAY_H_
+
+#include <variant>
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/column_overlay.h"
+#include "src/trace_processor/db/storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+// Overlay responsible for doing operations on storage.
+class StorageOverlay : public ColumnOverlay {
+ public:
+  explicit StorageOverlay(const Storage* storage) : storage_(storage) {}
+  void Filter(FilterOp, SqlValue, RowMap&) const override;
+  void StableSort(uint32_t* rows, uint32_t rows_size) const override;
+
+ private:
+  const Storage* storage_;
+};
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_OVERLAY_H_
diff --git a/src/trace_processor/db/storage_unittest.cc b/src/trace_processor/db/storage_unittest.cc
new file mode 100644
index 0000000..8988c00
--- /dev/null
+++ b/src/trace_processor/db/storage_unittest.cc
@@ -0,0 +1,294 @@
+/*
+ * 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.
+ */
+
+#include <numeric>
+#include "src/trace_processor/db/numeric_storage.h"
+
+#include "src/trace_processor/db/null_overlay.h"
+#include "src/trace_processor/db/storage_overlay.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+namespace {
+
+TEST(NumericStorageUnittest, StableSortTrivial) {
+  std::vector<uint32_t> data_vec{0, 1, 2, 0, 1, 2, 0, 1, 2};
+  std::vector<uint32_t> out = {0, 1, 2, 3, 4, 5, 6, 7, 8};
+
+  NumericStorage storage(data_vec.data(), 9, ColumnType::kUint32);
+  RowMap rm(0, 9);
+  storage.StableSort(out.data(), 9);
+
+  std::vector<uint32_t> stable_out{0, 3, 6, 1, 4, 7, 2, 5, 8};
+  ASSERT_EQ(out, stable_out);
+}
+
+TEST(NumericStorageUnittest, StableSort) {
+  std::vector<uint32_t> data_vec{0, 1, 2, 0, 1, 2, 0, 1, 2};
+  std::vector<uint32_t> out = {1, 7, 4, 0, 6, 3, 2, 5, 8};
+
+  NumericStorage storage(data_vec.data(), 9, ColumnType::kUint32);
+  RowMap rm(0, 9);
+  storage.StableSort(out.data(), 9);
+
+  std::vector<uint32_t> stable_out{0, 6, 3, 1, 7, 4, 2, 5, 8};
+  ASSERT_EQ(out, stable_out);
+}
+
+TEST(NumericStorageUnittest, CompareSlow) {
+  uint32_t size = 10;
+  std::vector<uint32_t> data_vec(size);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), size, ColumnType::kUint32);
+  BitVector::Builder builder(size);
+  storage.CompareSlow(FilterOp::kGe, SqlValue::Long(5), 0, size, builder);
+  BitVector bv = std::move(builder).Build();
+
+  ASSERT_EQ(bv.CountSetBits(), 5u);
+  ASSERT_EQ(bv.IndexOfNthSet(0), 5u);
+}
+
+TEST(NumericStorageUnittest, CompareSlowLarge) {
+  uint32_t size = 1025;
+  std::vector<uint32_t> data_vec(size);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), size, ColumnType::kUint32);
+  BitVector::Builder builder(size);
+  storage.CompareSlow(FilterOp::kGe, SqlValue::Long(5), 0, size, builder);
+  BitVector bv = std::move(builder).Build();
+
+  ASSERT_EQ(bv.CountSetBits(), 1020u);
+  ASSERT_EQ(bv.IndexOfNthSet(0), 5u);
+}
+
+TEST(NumericStorageUnittest, CompareFast) {
+  std::vector<uint32_t> data_vec(128);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), 128, ColumnType::kUint32);
+  BitVector::Builder builder(128);
+  storage.CompareFast(FilterOp::kGe, SqlValue::Long(100), 0, 128, builder);
+  BitVector bv = std::move(builder).Build();
+
+  ASSERT_EQ(bv.CountSetBits(), 28u);
+  ASSERT_EQ(bv.IndexOfNthSet(0), 100u);
+}
+
+TEST(NumericStorageUnittest, CompareSorted) {
+  std::vector<uint32_t> data_vec(128);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), 128, ColumnType::kUint32);
+  RowMap rm(0, 128);
+  storage.CompareSorted(FilterOp::kGe, SqlValue::Long(100), rm);
+
+  ASSERT_EQ(rm.size(), 28u);
+  ASSERT_EQ(rm.Get(0), 100u);
+}
+
+TEST(NumericStorageUnittest, CompareSortedIndexesGreaterEqual) {
+  std::vector<uint32_t> data_vec{30, 40, 50, 60, 90, 80, 70, 0, 10, 20};
+  std::vector<uint32_t> sorted_order{7, 8, 9, 0, 1, 2, 3, 6, 5, 4};
+
+  NumericStorage storage(data_vec.data(), 10, ColumnType::kUint32);
+  RowMap rm(0, 10);
+
+  storage.CompareSortedIndexes(FilterOp::kGe, SqlValue::Long(60),
+                               sorted_order.data(), rm);
+
+  ASSERT_EQ(rm.size(), 4u);
+  ASSERT_EQ(rm.Get(0), 3u);
+  ASSERT_EQ(rm.Get(1), 6u);
+  ASSERT_EQ(rm.Get(2), 5u);
+  ASSERT_EQ(rm.Get(3), 4u);
+}
+
+TEST(NumericStorageUnittest, CompareSortedIndexesLess) {
+  std::vector<uint32_t> data_vec{30, 40, 50, 60, 90, 80, 70, 0, 10, 20};
+  std::vector<uint32_t> sorted_order{7, 8, 9, 0, 1, 2, 3, 6, 5, 4};
+
+  NumericStorage storage(data_vec.data(), 10, ColumnType::kUint32);
+  RowMap rm(0, 10);
+
+  storage.CompareSortedIndexes(FilterOp::kLt, SqlValue::Long(60),
+                               sorted_order.data(), rm);
+
+  ASSERT_EQ(rm.size(), 6u);
+  ASSERT_EQ(rm.Get(0), 7u);
+}
+
+TEST(NumericStorageUnittest, CompareSortedIndexesEqual) {
+  std::vector<uint32_t> data_vec{30, 40, 50, 60, 90, 80, 70, 0, 10, 20};
+  std::vector<uint32_t> sorted_order{7, 8, 9, 0, 1, 2, 3, 6, 5, 4};
+
+  NumericStorage storage(data_vec.data(), 10, ColumnType::kUint32);
+  RowMap rm(0, 10);
+
+  storage.CompareSortedIndexes(FilterOp::kEq, SqlValue::Long(60),
+                               sorted_order.data(), rm);
+
+  ASSERT_EQ(rm.size(), 1u);
+  ASSERT_EQ(rm.Get(0), 3u);
+}
+
+TEST(StorageOverlayUnittests, FilterIsNull) {
+  std::vector<uint32_t> data_vec(1025);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), 1025, ColumnType::kUint32);
+  StorageOverlay overlay(&storage);
+
+  RowMap rm(0, 1025);
+  overlay.Filter(FilterOp::kIsNull, SqlValue::Long(0), rm);
+
+  ASSERT_EQ(rm.size(), 0u);
+}
+
+TEST(StorageOverlayUnittests, FilterIsNotNull) {
+  std::vector<uint32_t> data_vec(1025);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), 1025, ColumnType::kUint32);
+  StorageOverlay overlay(&storage);
+
+  RowMap rm(0, 1025);
+  overlay.Filter(FilterOp::kIsNotNull, SqlValue::Long(0), rm);
+
+  ASSERT_EQ(rm.size(), 1025u);
+}
+
+TEST(StorageOverlayUnittests, Filter) {
+  std::vector<uint32_t> data_vec(1025);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), 1025, ColumnType::kUint32);
+  StorageOverlay overlay(&storage);
+
+  RowMap rm(0, 1025);
+  overlay.Filter(FilterOp::kGe, SqlValue::Long(200), rm);
+
+  ASSERT_EQ(rm.size(), 825u);
+  ASSERT_EQ(rm.Get(0), 200u);
+}
+
+TEST(StorageOverlayUnittests, Sort) {
+  std::vector<uint32_t> data_vec{0, 1, 2, 0, 1, 2, 0, 1, 2};
+  std::vector<uint32_t> out = {1, 7, 4, 0, 6, 3, 2, 5, 8};
+
+  NumericStorage storage(data_vec.data(), 9, ColumnType::kUint32);
+  StorageOverlay overlay(&storage);
+
+  overlay.StableSort(out.data(), 9);
+
+  std::vector<uint32_t> stable_out{0, 6, 3, 1, 7, 4, 2, 5, 8};
+  ASSERT_EQ(out, stable_out);
+}
+
+TEST(NullOverlayUnittest, FilterIsNull) {
+  std::vector<uint32_t> data_vec(10);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  BitVector bv = BitVector::Range(0, 20, [](uint32_t t) { return t % 2 == 0; });
+
+  NumericStorage storage(data_vec.data(), 10, ColumnType::kUint32);
+  std::unique_ptr<ColumnOverlay> storage_overlay(new StorageOverlay(&storage));
+  NullOverlay overlay(std::move(storage_overlay), &bv);
+
+  RowMap rm(0, 10);
+  overlay.Filter(FilterOp::kIsNull, SqlValue::Long(5), rm);
+  ASSERT_EQ(rm.size(), 5u);
+  ASSERT_EQ(rm.Get(0), 1u);
+}
+
+TEST(NullOverlayUnittest, FilterIsNotNull) {
+  std::vector<uint32_t> data_vec(10);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), 10, ColumnType::kUint32);
+  BitVector bv = BitVector::Range(0, 20, [](uint32_t t) { return t % 2 == 0; });
+  std::unique_ptr<ColumnOverlay> storage_overlay(new StorageOverlay(&storage));
+  NullOverlay overlay(std::move(storage_overlay), &bv);
+
+  RowMap rm(0, 10);
+  overlay.Filter(FilterOp::kIsNotNull, SqlValue::Long(5), rm);
+
+  ASSERT_EQ(rm.size(), 5u);
+  ASSERT_EQ(rm.Get(0), 0u);
+}
+
+TEST(NullOverlayUnittest, Filter) {
+  uint32_t bv_size = 20;
+  uint32_t data_size = 10;
+
+  // Prepare storage
+  std::vector<uint32_t> data_vec(data_size);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), data_size, ColumnType::kUint32);
+
+  // Prepare NullOverlay
+  BitVector bv =
+      BitVector::Range(0, bv_size, [](uint32_t t) { return t % 2 == 0; });
+  std::unique_ptr<ColumnOverlay> storage_overlay(new StorageOverlay(&storage));
+  NullOverlay overlay(std::move(storage_overlay), &bv);
+
+  RowMap rm(0, bv_size);
+  overlay.Filter(FilterOp::kGe, SqlValue::Long(5), rm);
+
+  ASSERT_EQ(rm.size(), 5u);
+  ASSERT_EQ(rm.Get(0), 10u);
+}
+
+TEST(NullOverlayUnittest, FilterLarge) {
+  uint32_t bv_size = 1000;
+  uint32_t data_size = 100;
+
+  // Prepare storage
+  std::vector<uint32_t> data_vec(data_size);
+  std::iota(data_vec.begin(), data_vec.end(), 0);
+  NumericStorage storage(data_vec.data(), data_size, ColumnType::kUint32);
+
+  // Prepare NullOverlay
+  BitVector bv =
+      BitVector::Range(800, bv_size, [](uint32_t t) { return t % 2 == 0; });
+  std::unique_ptr<ColumnOverlay> storage_overlay(new StorageOverlay(&storage));
+  NullOverlay overlay(std::move(storage_overlay), &bv);
+
+  RowMap rm(0, bv_size);
+  overlay.Filter(FilterOp::kGe, SqlValue::Long(50), rm);
+
+  ASSERT_EQ(rm.size(), 50u);
+  ASSERT_EQ(rm.Get(0), 900u);
+}
+
+TEST(NullOverlayUnittest, Sort) {
+  // Prepare storage
+  std::vector<uint32_t> data_vec{0, 1, 2, 0, 1, 2, 0, 1, 2};
+  std::vector<uint32_t> out(18);
+  std::iota(out.begin(), out.end(), 0);
+  NumericStorage storage(data_vec.data(), 9, ColumnType::kUint32);
+
+  // Prepare NullOverlay
+  BitVector bv = BitVector::Range(0, 18, [](uint32_t t) { return t % 2 == 0; });
+  std::unique_ptr<ColumnOverlay> storage_overlay(new StorageOverlay(&storage));
+  NullOverlay overlay(std::move(storage_overlay), &bv);
+
+  overlay.StableSort(out.data(), 18);
+
+  std::vector<uint32_t> stable_out{1, 3, 5,  7, 9, 11, 13, 15, 17,
+                                   0, 6, 12, 2, 8, 14, 4,  10, 16};
+  ASSERT_EQ(out, stable_out);
+}
+
+}  // namespace
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage_variants.h b/src/trace_processor/db/storage_variants.h
new file mode 100644
index 0000000..bc169b9
--- /dev/null
+++ b/src/trace_processor/db/storage_variants.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef SRC_TRACE_PROCESSOR_DB_STORAGE_VARIANTS_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_VARIANTS_H_
+
+#include <variant>
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/storage.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace column {
+
+// All viable numeric values for ColumnTypes.
+using NumericValue = std::variant<uint32_t, int32_t, int64_t, double_t>;
+
+// Using the fact that binary operators in std are operators() of classes, we
+// can wrap those classes in variants and use them for std::visit in
+// SerialComparators. This helps prevent excess templating and switches.
+template <typename T>
+using FilterOpVariant = std::variant<std::greater<T>,
+                                     std::greater_equal<T>,
+                                     std::less<T>,
+                                     std::less_equal<T>,
+                                     std::equal_to<T>,
+                                     std::not_equal_to<T>>;
+
+// Based on SqlValue and ColumnType, casts SqlValue to proper type, returns
+// std::nullopt if SqlValue can't be cast and should be considered invalid for
+// comparison.
+inline std::optional<NumericValue> GetNumericTypeVariant(ColumnType type,
+                                                         SqlValue val) {
+  if (val.is_null())
+    return std::nullopt;
+
+  switch (type) {
+    case ColumnType::kDouble:
+      return val.AsDouble();
+    case ColumnType::kInt64:
+      return val.AsLong();
+    case ColumnType::kInt32:
+      if (val.AsLong() > std::numeric_limits<int32_t>::max() ||
+          val.AsLong() < std::numeric_limits<int32_t>::min())
+        return std::nullopt;
+      return static_cast<int32_t>(val.AsLong());
+    case ColumnType::kUint32:
+      if (val.AsLong() > std::numeric_limits<uint32_t>::max() ||
+          val.AsLong() < std::numeric_limits<uint32_t>::min())
+        return std::nullopt;
+      return static_cast<uint32_t>(val.AsLong());
+    case ColumnType::kString:
+    case ColumnType::kDummy:
+    case ColumnType::kId:
+      return std::nullopt;
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+// Based on SqlValue and ColumnType, casts SqlValue to proper type, returns
+// std::nullopt if SqlValue can't be cast and should be considered invalid for
+// comparison.
+inline std::optional<NumericValue> GetNumericTypeVariant(ColumnType type) {
+  return GetNumericTypeVariant(type, SqlValue::Long(0));
+}
+
+// Fetch std binary comparator class based on FilterOp. Can be used in
+// std::visit for comparison.
+template <typename T>
+inline FilterOpVariant<T> GetFilterOpVariant(FilterOp op) {
+  switch (op) {
+    case FilterOp::kEq:
+      return FilterOpVariant<T>(std::equal_to<T>());
+    case FilterOp::kNe:
+      return FilterOpVariant<T>(std::not_equal_to<T>());
+    case FilterOp::kGe:
+      return FilterOpVariant<T>(std::greater_equal<T>());
+    case FilterOp::kGt:
+      return FilterOpVariant<T>(std::greater<T>());
+    case FilterOp::kLe:
+      return FilterOpVariant<T>(std::less_equal<T>());
+    case FilterOp::kLt:
+      return FilterOpVariant<T>(std::less<T>());
+    case FilterOp::kGlob:
+    case FilterOp::kIsNotNull:
+    case FilterOp::kIsNull:
+      PERFETTO_FATAL("Not a valid operation on numeric type.");
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+}  // namespace column
+}  // namespace trace_processor
+}  // namespace perfetto
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_VARIANTS_H_
diff --git a/src/trace_processor/db/table_unittest.cc b/src/trace_processor/db/table_unittest.cc
deleted file mode 100644
index 6f370ca..0000000
--- a/src/trace_processor/db/table_unittest.cc
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-#include "src/trace_processor/db/table.h"
-#include "src/trace_processor/db/typed_column.h"
-#include "src/trace_processor/tables/macros.h"
-
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace {
-
-#define PERFETTO_TP_TEST_EVENT_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestEventTable, "event")                           \
-  PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)            \
-  C(int64_t, ts, Column::Flag::kSorted)                   \
-  C(int64_t, dur)                                         \
-  C(uint32_t, arg_set_id, Column::Flag::kSorted | Column::Flag::kSetId)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_EVENT_TABLE_DEF);
-
-TestEventTable::~TestEventTable() = default;
-
-TEST(TableTest, SetIdColumns) {
-  StringPool pool;
-  TestEventTable table{&pool, nullptr};
-
-  table.Insert(TestEventTable::Row(0, 0, 0));
-  table.Insert(TestEventTable::Row(1, 0, 0));
-  table.Insert(TestEventTable::Row(2, 0, 2));
-  table.Insert(TestEventTable::Row(3, 0, 3));
-  table.Insert(TestEventTable::Row(4, 0, 4));
-  table.Insert(TestEventTable::Row(5, 0, 4));
-  table.Insert(TestEventTable::Row(6, 0, 4));
-  table.Insert(TestEventTable::Row(7, 0, 4));
-  table.Insert(TestEventTable::Row(8, 0, 8));
-
-  ASSERT_EQ(table.row_count(), 9u);
-  ASSERT_TRUE(table.arg_set_id().IsSetId());
-
-  // Verify that not-present ids are not returned.
-  {
-    static constexpr uint32_t kFilterArgSetId = 1;
-    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
-    ASSERT_EQ(res.row_count(), 0u);
-  }
-  {
-    static constexpr uint32_t kFilterArgSetId = 9;
-    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
-    ASSERT_EQ(res.row_count(), 0u);
-  }
-
-  // Verify that kSetId flag is correctly removed after filtering/sorting.
-  {
-    static constexpr uint32_t kFilterArgSetId = 3;
-    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
-    ASSERT_EQ(res.row_count(), 1u);
-    ASSERT_FALSE(res.GetColumnByName("arg_set_id")->IsSetId());
-  }
-  {
-    auto res = table.Sort({table.dur().descending()});
-    ASSERT_FALSE(res.GetColumnByName("arg_set_id")->IsSetId());
-  }
-
-  uint32_t arg_set_id_col_idx =
-      static_cast<uint32_t>(TestEventTable::ColumnIndex::arg_set_id);
-
-  // Verify that filtering equality for real arg set ids works as expected.
-  {
-    static constexpr uint32_t kFilterArgSetId = 4;
-    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
-    ASSERT_EQ(res.row_count(), 4u);
-    for (auto it = res.IterateRows(); it; it.Next()) {
-      uint32_t arg_set_id =
-          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
-      ASSERT_EQ(arg_set_id, kFilterArgSetId);
-    }
-  }
-  {
-    static constexpr uint32_t kFilterArgSetId = 0;
-    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
-    ASSERT_EQ(res.row_count(), 2u);
-    for (auto it = res.IterateRows(); it; it.Next()) {
-      uint32_t arg_set_id =
-          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
-      ASSERT_EQ(arg_set_id, kFilterArgSetId);
-    }
-  }
-  {
-    static constexpr uint32_t kFilterArgSetId = 8;
-    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
-    ASSERT_EQ(res.row_count(), 1u);
-    for (auto it = res.IterateRows(); it; it.Next()) {
-      uint32_t arg_set_id =
-          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
-      ASSERT_EQ(arg_set_id, kFilterArgSetId);
-    }
-  }
-
-  // Verify that filtering equality for arg set ids after filtering another
-  // column works.
-  {
-    static constexpr uint32_t kFilterArgSetId = 4;
-    auto res = table.Filter(
-        {table.ts().ge(6), table.arg_set_id().eq(kFilterArgSetId)});
-    ASSERT_EQ(res.row_count(), 2u);
-    for (auto it = res.IterateRows(); it; it.Next()) {
-      uint32_t arg_set_id =
-          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
-      ASSERT_EQ(arg_set_id, kFilterArgSetId);
-    }
-  }
-}
-
-}  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/db/view_unittest.cc b/src/trace_processor/db/view_unittest.cc
index 2507b92..221753a 100644
--- a/src/trace_processor/db/view_unittest.cc
+++ b/src/trace_processor/db/view_unittest.cc
@@ -15,53 +15,23 @@
  */
 
 #include "src/trace_processor/db/view.h"
-#include "src/trace_processor/tables/macros.h"
+#include "src/trace_processor/db/view_unittest_py.h"
 #include "src/trace_processor/views/macros.h"
 
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
 namespace trace_processor {
+namespace tables {
+
+ViewThreadTable::~ViewThreadTable() = default;
+ViewTrackTable::~ViewTrackTable() = default;
+ViewThreadTrackTable::~ViewThreadTrackTable() = default;
+ViewEventTable::~ViewEventTable() = default;
+ViewSliceTable::~ViewSliceTable() = default;
+
 namespace {
 
-#define PERFETTO_TP_TEST_THREAD_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestThreadTable, "thread_table")                    \
-  PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)             \
-  C(StringPool::Id, name)                                  \
-  C(uint32_t, tid)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_THREAD_TABLE_DEF);
-
-#define PERFETTO_TP_TEST_TRACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestTrackTable, "track_table")                     \
-  PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)            \
-  C(StringPool::Id, name)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_TRACK_TABLE_DEF);
-
-#define PERFETTO_TP_TEST_THREAD_TRACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestThreadTrackTable, "thread_track_table")               \
-  PARENT(PERFETTO_TP_TEST_TRACK_TABLE_DEF, C)                    \
-  C(TestThreadTable::Id, utid)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_THREAD_TRACK_TABLE_DEF);
-
-#define PERFETTO_TP_TEST_EVENT_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestEventTable, "event_table")                     \
-  PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)            \
-  C(int64_t, ts, Column::Flag::kSorted)                   \
-  C(TestTrackTable::Id, track_id)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_EVENT_TABLE_DEF);
-
-#define PERFETTO_TP_TEST_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestSliceTable, "slice_table")                     \
-  PARENT(PERFETTO_TP_TEST_EVENT_TABLE_DEF, C)             \
-  C(StringPool::Id, name)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_SLICE_TABLE_DEF);
-
-TestThreadTable::~TestThreadTable() = default;
-TestTrackTable::~TestTrackTable() = default;
-TestThreadTrackTable::~TestThreadTrackTable() = default;
-TestEventTable::~TestEventTable() = default;
-TestSliceTable::~TestSliceTable() = default;
-
 template <typename ViewSubclass>
 class AbstractViewTest : public ::testing::Test {
  protected:
@@ -106,17 +76,17 @@
 };
 
 #define PERFETTO_TP_EVENT_VIEW_DEF(NAME, FROM, JOIN, COL, _)               \
-  NAME(TestEventView, "event_view")                                        \
-  FROM(TestEventTable, event)                                              \
-  JOIN(TestTrackTable, track, id, event, track_id, View::kIdAlwaysPresent) \
+  NAME(ViewEventView, "event_view")                                        \
+  FROM(ViewEventTable, event)                                              \
+  JOIN(ViewTrackTable, track, id, event, track_id, View::kIdAlwaysPresent) \
   COL(id, event, id)                                                       \
   COL(ts, event, ts)                                                       \
   COL(track_id, event, track_id)                                           \
   COL(track_name, track, name)
 PERFETTO_TP_DECLARE_VIEW(PERFETTO_TP_EVENT_VIEW_DEF);
-PERFETTO_TP_DEFINE_VIEW(TestEventView);
+PERFETTO_TP_DEFINE_VIEW(ViewEventView);
 
-class EventViewTest : public AbstractViewTest<TestEventView> {
+class EventViewTest : public AbstractViewTest<ViewEventView> {
  protected:
   EventViewTest() {
     t1_id_ = track_.Insert({/* name */ Intern("foo")}).id;
@@ -127,26 +97,26 @@
     event_table_.Insert({/* ts */ 102, t1_id_});
   }
 
-  virtual TestEventView& view() override { return event_view_; }
+  virtual ViewEventView& view() override { return event_view_; }
 
-  TestTrackTable::Id t1_id_;
-  TestTrackTable::Id t2_id_;
+  ViewTrackTable::Id t1_id_;
+  ViewTrackTable::Id t2_id_;
 
  private:
-  TestEventTable event_table_{&pool_, nullptr};
-  TestTrackTable track_{&pool_, nullptr};
-  TestEventView event_view_{&event_table_, &track_};
+  ViewEventTable event_table_{&pool_};
+  ViewTrackTable track_{&pool_};
+  ViewEventView event_view_{&event_table_, &track_};
 };
 
 TEST_F(EventViewTest, UnusedColumnsAreDummy) {
-  TestEventView::QueryResult result = QueryUsingCols({ColIdx::track_name});
+  ViewEventView::QueryResult result = QueryUsingCols({ColIdx::track_name});
   ASSERT_TRUE(result.columns()[ColIdx::id].IsDummy());
   ASSERT_TRUE(result.columns()[ColIdx::ts].IsDummy());
   ASSERT_FALSE(result.columns()[ColIdx::track_name].IsDummy());
 }
 
 TEST_F(EventViewTest, Iterate) {
-  TestEventView::QueryResult result = Query();
+  ViewEventView::QueryResult result = Query();
   auto it = result.IterateRows();
   ASSERT_TRUE(it);
   ASSERT_EQ(it.row_number().row_number(), 0u);
@@ -170,13 +140,13 @@
 }
 
 TEST_F(EventViewTest, FilterEventEmpty) {
-  TestEventView::QueryResult result = Query({view().ts().eq(0)});
+  ViewEventView::QueryResult result = Query({view().ts().eq(0)});
   auto it = result.IterateRows();
   ASSERT_FALSE(it);
 }
 
 TEST_F(EventViewTest, FilterEventNoUseTrack) {
-  TestEventView::QueryResult result =
+  ViewEventView::QueryResult result =
       Query({view().ts().eq(100)}, {}, {ColIdx::ts});
   auto it = result.IterateRows();
   ASSERT_TRUE(it);
@@ -186,7 +156,7 @@
 }
 
 TEST_F(EventViewTest, FilterEventUseTrack) {
-  TestEventView::QueryResult result =
+  ViewEventView::QueryResult result =
       Query({view().ts().eq(100)}, {},
             {ColIdx::ts, ColIdx::track_name, ColIdx::track_id});
   auto it = result.IterateRows();
@@ -199,13 +169,13 @@
 }
 
 TEST_F(EventViewTest, FilterTrackEmpty) {
-  TestEventView::QueryResult result = Query({view().track_id().eq(102398)});
+  ViewEventView::QueryResult result = Query({view().track_id().eq(102398)});
   auto it = result.IterateRows();
   ASSERT_FALSE(it);
 }
 
 TEST_F(EventViewTest, FilterTrackNoUseEvent) {
-  TestEventView::QueryResult result =
+  ViewEventView::QueryResult result =
       Query({view().track_name().eq("foo")}, {},
             {ColIdx::track_name, ColIdx::track_id});
   auto it = result.IterateRows();
@@ -221,7 +191,7 @@
 }
 
 TEST_F(EventViewTest, FilterTrackUseEvent) {
-  TestEventView::QueryResult result =
+  ViewEventView::QueryResult result =
       Query({view().track_id().eq(t1_id_.value)}, {},
             {ColIdx::ts, ColIdx::track_name, ColIdx::track_id});
   auto it = result.IterateRows();
@@ -239,10 +209,10 @@
 }
 
 #define PERFETTO_TP_THREAD_EVENT_VIEW_DEF(NAME, FROM, JOIN, COL, _)      \
-  NAME(TestThreadEventView, "thread_event_view")                         \
-  FROM(TestEventTable, event)                                            \
-  JOIN(TestThreadTrackTable, track, id, event, track_id, View::kNoFlag)  \
-  JOIN(TestThreadTable, thread, id, track, utid, View::kIdAlwaysPresent) \
+  NAME(ViewThreadEventView, "thread_event_view")                         \
+  FROM(ViewEventTable, event)                                            \
+  JOIN(ViewThreadTrackTable, track, id, event, track_id, View::kNoFlag)  \
+  JOIN(ViewThreadTable, thread, id, track, utid, View::kIdAlwaysPresent) \
   COL(id, event, id)                                                     \
   COL(ts, event, ts)                                                     \
   COL(track_id, track, id)                                               \
@@ -250,9 +220,9 @@
   COL(utid, track, utid)                                                 \
   COL(thread_name, thread, name)
 PERFETTO_TP_DECLARE_VIEW(PERFETTO_TP_THREAD_EVENT_VIEW_DEF);
-PERFETTO_TP_DEFINE_VIEW(TestThreadEventView);
+PERFETTO_TP_DEFINE_VIEW(ViewThreadEventView);
 
-class ThreadEventViewTest : public AbstractViewTest<TestThreadEventView> {
+class ThreadEventViewTest : public AbstractViewTest<ViewThreadEventView> {
  protected:
   ThreadEventViewTest() {
     th1_id_ = thread_.Insert({Intern("th1"), 1}).id;
@@ -275,24 +245,24 @@
     event_table_.Insert({/* ts */ 107, t4_id_});
   }
 
-  virtual TestThreadEventView& view() override { return event_view_; }
+  virtual ViewThreadEventView& view() override { return event_view_; }
 
-  TestThreadTable::Id th1_id_;
-  TestThreadTable::Id th2_id_;
+  ViewThreadTable::Id th1_id_;
+  ViewThreadTable::Id th2_id_;
 
-  TestTrackTable::Id t1_id_;
-  TestTrackTable::Id t2_id_;
-  TestTrackTable::Id t3_id_;
-  TestTrackTable::Id t4_id_;
-  TestTrackTable::Id t5_id_;
-  TestTrackTable::Id t6_id_;
+  ViewTrackTable::Id t1_id_;
+  ViewTrackTable::Id t2_id_;
+  ViewTrackTable::Id t3_id_;
+  ViewTrackTable::Id t4_id_;
+  ViewTrackTable::Id t5_id_;
+  ViewTrackTable::Id t6_id_;
 
  private:
-  TestEventTable event_table_{&pool_, nullptr};
-  TestTrackTable track_{&pool_, nullptr};
-  TestThreadTrackTable thread_track_{&pool_, &track_};
-  TestThreadTable thread_{&pool_, nullptr};
-  TestThreadEventView event_view_{&event_table_, &thread_track_, &thread_};
+  ViewEventTable event_table_{&pool_};
+  ViewTrackTable track_{&pool_};
+  ViewThreadTrackTable thread_track_{&pool_, &track_};
+  ViewThreadTable thread_{&pool_};
+  ViewThreadEventView event_view_{&event_table_, &thread_track_, &thread_};
 };
 
 TEST_F(ThreadEventViewTest, Iterate) {
@@ -427,7 +397,7 @@
 }
 
 #define PERFETTO_TP_THREAD_SLICE_VIEW_DEF(NAME, FROM, JOIN, COL, _)     \
-  NAME(TestThreadSliceView, "thread_slice_view")                        \
+  NAME(ViewThreadSliceView, "thread_slice_view")                        \
   COL(id, slice, id)                                                    \
   COL(ts, slice, ts)                                                    \
   COL(name, slice, name)                                                \
@@ -435,13 +405,13 @@
   COL(track_name, track, name)                                          \
   COL(utid, thread, id)                                                 \
   COL(thread_name, thread, name)                                        \
-  FROM(TestSliceTable, slice)                                           \
-  JOIN(TestThreadTrackTable, track, id, slice, track_id, View::kNoFlag) \
-  JOIN(TestThreadTable, thread, id, track, utid, View::kIdAlwaysPresent)
+  FROM(ViewSliceTable, slice)                                           \
+  JOIN(ViewThreadTrackTable, track, id, slice, track_id, View::kNoFlag) \
+  JOIN(ViewThreadTable, thread, id, track, utid, View::kIdAlwaysPresent)
 PERFETTO_TP_DECLARE_VIEW(PERFETTO_TP_THREAD_SLICE_VIEW_DEF);
-PERFETTO_TP_DEFINE_VIEW(TestThreadSliceView);
+PERFETTO_TP_DEFINE_VIEW(ViewThreadSliceView);
 
-class ThreadSliceViewTest : public AbstractViewTest<TestThreadSliceView> {
+class ThreadSliceViewTest : public AbstractViewTest<ViewThreadSliceView> {
  protected:
   ThreadSliceViewTest() {
     th1_id_ = thread_.Insert({Intern("th1"), 1}).id;
@@ -464,25 +434,25 @@
     slice_table_.Insert({/* ts */ 107, t4_id_, Intern("ts107")});
   }
 
-  TestThreadSliceView& view() override { return slice_view_; }
+  ViewThreadSliceView& view() override { return slice_view_; }
 
-  TestThreadTable::Id th1_id_;
-  TestThreadTable::Id th2_id_;
+  ViewThreadTable::Id th1_id_;
+  ViewThreadTable::Id th2_id_;
 
-  TestTrackTable::Id t1_id_;
-  TestTrackTable::Id t2_id_;
-  TestTrackTable::Id t3_id_;
-  TestTrackTable::Id t4_id_;
-  TestTrackTable::Id t5_id_;
-  TestTrackTable::Id t6_id_;
+  ViewTrackTable::Id t1_id_;
+  ViewTrackTable::Id t2_id_;
+  ViewTrackTable::Id t3_id_;
+  ViewTrackTable::Id t4_id_;
+  ViewTrackTable::Id t5_id_;
+  ViewTrackTable::Id t6_id_;
 
  private:
-  TestEventTable event_{&pool_, nullptr};
-  TestSliceTable slice_table_{&pool_, &event_};
-  TestTrackTable track_{&pool_, nullptr};
-  TestThreadTrackTable thread_track_{&pool_, &track_};
-  TestThreadTable thread_{&pool_, nullptr};
-  TestThreadSliceView slice_view_{&slice_table_, &thread_track_, &thread_};
+  ViewEventTable event_{&pool_};
+  ViewSliceTable slice_table_{&pool_, &event_};
+  ViewTrackTable track_{&pool_};
+  ViewThreadTrackTable thread_track_{&pool_, &track_};
+  ViewThreadTable thread_{&pool_};
+  ViewThreadSliceView slice_view_{&slice_table_, &thread_track_, &thread_};
 };
 
 TEST_F(ThreadSliceViewTest, Iterate) {
@@ -600,5 +570,6 @@
 }
 
 }  // namespace
+}  // namespace tables
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/view_unittest.py b/src/trace_processor/db/view_unittest.py
new file mode 100644
index 0000000..a7f7abd
--- /dev/null
+++ b/src/trace_processor/db/view_unittest.py
@@ -0,0 +1,75 @@
+# 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.
+"""Contains tables for unitviewing."""
+
+from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnFlag
+from python.generators.trace_processor_table.public import Table
+from python.generators.trace_processor_table.public import CppTableId
+from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import CppInt64
+from python.generators.trace_processor_table.public import CppString
+
+VIEW_THREAD_TABLE = Table(
+    python_module=__file__,
+    class_name="ViewThreadTable",
+    sql_name="thread_table",
+    columns=[
+        C("name", CppString()),
+        C("tid", CppUint32()),
+    ])
+
+VIEW_TRACK_TABLE = Table(
+    python_module=__file__,
+    class_name="ViewTrackTable",
+    sql_name="track_table",
+    columns=[
+        C("name", CppString()),
+    ])
+
+VIEW_THREAD_TRACK_TABLE = Table(
+    python_module=__file__,
+    class_name="ViewThreadTrackTable",
+    sql_name="thread_track_table",
+    parent=VIEW_TRACK_TABLE,
+    columns=[
+        C("utid", CppTableId(VIEW_THREAD_TABLE)),
+    ])
+
+VIEW_EVENT_TABLE = Table(
+    python_module=__file__,
+    class_name="ViewEventTable",
+    sql_name="event_table",
+    columns=[
+        C("ts", CppInt64(), flags=ColumnFlag.SORTED),
+        C("track_id", CppTableId(VIEW_TRACK_TABLE)),
+    ])
+
+VIEW_SLICE_TABLE = Table(
+    python_module=__file__,
+    class_name="ViewSliceTable",
+    sql_name="slice_table",
+    parent=VIEW_EVENT_TABLE,
+    columns=[
+        C("name", CppString()),
+    ])
+
+# Keep this list sorted.
+ALL_TABLES = [
+    VIEW_EVENT_TABLE,
+    VIEW_SLICE_TABLE,
+    VIEW_THREAD_TABLE,
+    VIEW_THREAD_TRACK_TABLE,
+    VIEW_TRACK_TABLE,
+]
diff --git a/src/trace_processor/importers/common/args_translation_table.cc b/src/trace_processor/importers/common/args_translation_table.cc
index 74cdc34..8aa9a0d 100644
--- a/src/trace_processor/importers/common/args_translation_table.cc
+++ b/src/trace_processor/importers/common/args_translation_table.cc
@@ -52,25 +52,6 @@
 
 }  // namespace
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-constexpr char ArgsTranslationTable::kChromeHistogramHashKey[];
-constexpr char ArgsTranslationTable::kChromeHistogramNameKey[];
-
-constexpr char ArgsTranslationTable::kChromeUserEventHashKey[];
-constexpr char ArgsTranslationTable::kChromeUserEventActionKey[];
-
-constexpr char ArgsTranslationTable::kChromePerformanceMarkSiteHashKey[];
-constexpr char ArgsTranslationTable::kChromePerformanceMarkSiteKey[];
-
-constexpr char ArgsTranslationTable::kChromePerformanceMarkMarkHashKey[];
-constexpr char ArgsTranslationTable::kChromePerformanceMarkMarkKey[];
-
-constexpr char ArgsTranslationTable::kMojoMethodMappingIdKey[];
-constexpr char ArgsTranslationTable::kMojoMethodRelPcKey[];
-constexpr char ArgsTranslationTable::kMojoMethodNameKey[];
-constexpr char ArgsTranslationTable::kMojoIntefaceTagKey[];
-#endif
-
 ArgsTranslationTable::ArgsTranslationTable(TraceStorage* storage)
     : storage_(storage),
       interned_chrome_histogram_hash_key_(
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 07bd848..f6a99f1 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -16,12 +16,43 @@
 
 #include "src/trace_processor/importers/common/track_tracker.h"
 
+#include <optional>
+
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
 
 namespace perfetto {
 namespace trace_processor {
 
+namespace {
+
+const char* GetNameForGroup(TrackTracker::Group group) {
+  switch (group) {
+    case TrackTracker::Group::kMemory:
+      return "Memory";
+    case TrackTracker::Group::kIo:
+      return "IO";
+    case TrackTracker::Group::kVirtio:
+      return "Virtio";
+    case TrackTracker::Group::kNetwork:
+      return "Network";
+    case TrackTracker::Group::kPower:
+      return "Power";
+    case TrackTracker::Group::kDeviceState:
+      return "Device State";
+    case TrackTracker::Group::kThermals:
+      return "Thermals";
+    case TrackTracker::Group::kClockFrequency:
+      return "Clock Freqeuncy";
+    case TrackTracker::Group::kSizeSentinel:
+      PERFETTO_FATAL("Unexpected size passed as group");
+  }
+  PERFETTO_FATAL("For GCC");
+}
+
+}  // namespace
+
 TrackTracker::TrackTracker(TraceProcessorContext* context)
     : source_key_(context->storage->InternString("source")),
       source_id_key_(context->storage->InternString("source_id")),
@@ -193,7 +224,8 @@
   return *trigger_track_id_;
 }
 
-TrackId TrackTracker::InternGlobalCounterTrack(StringId name,
+TrackId TrackTracker::InternGlobalCounterTrack(TrackTracker::Group group,
+                                               StringId name,
                                                SetArgsCallback callback,
                                                StringId unit,
                                                StringId description) {
@@ -203,6 +235,7 @@
   }
 
   tables::CounterTrackTable::Row row(name);
+  row.parent_id = InternTrackForGroup(group);
   row.unit = unit;
   row.description = description;
   TrackId track =
@@ -380,5 +413,18 @@
   return context_->storage->mutable_perf_counter_track_table()->Insert(row).id;
 }
 
+TrackId TrackTracker::InternTrackForGroup(TrackTracker::Group group) {
+  uint32_t group_idx = static_cast<uint32_t>(group);
+  const std::optional<TrackId>& group_id = group_track_ids_[group_idx];
+  if (group_id) {
+    return *group_id;
+  }
+
+  StringId id = context_->storage->InternString(GetNameForGroup(group));
+  TrackId track_id = context_->storage->mutable_track_table()->Insert({id}).id;
+  group_track_ids_[group_idx] = track_id;
+  return track_id;
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/common/track_tracker.h b/src/trace_processor/importers/common/track_tracker.h
index c4a4232..99076f3 100644
--- a/src/trace_processor/importers/common/track_tracker.h
+++ b/src/trace_processor/importers/common/track_tracker.h
@@ -17,6 +17,7 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACK_TRACKER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_TRACK_TRACKER_H_
 
+#include <optional>
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
@@ -27,6 +28,23 @@
 // Tracks and stores tracks based on track types, ids and scopes.
 class TrackTracker {
  public:
+  // Enum which groups global tracks to avoid an explosion of tracks at the top
+  // level.
+  // Try and keep members of this enum high level as every entry here
+  // corresponds to ~1 extra UI track.
+  enum class Group : uint32_t {
+    kMemory = 0,
+    kIo,
+    kVirtio,
+    kNetwork,
+    kPower,
+    kDeviceState,
+    kThermals,
+    kClockFrequency,
+
+    // Keep this last.
+    kSizeSentinel,
+  };
   using SetArgsCallback = std::function<void(ArgsTracker::BoundInserter&)>;
 
   explicit TrackTracker(TraceProcessorContext*);
@@ -67,7 +85,8 @@
   TrackId GetOrCreateTriggerTrack();
 
   // Interns a global counter track into the storage.
-  TrackId InternGlobalCounterTrack(StringId name,
+  TrackId InternGlobalCounterTrack(Group group,
+                                   StringId name,
                                    SetArgsCallback = {},
                                    StringId unit = kNullStringId,
                                    StringId description = kNullStringId);
@@ -155,6 +174,12 @@
              std::tie(r.source_id, r.upid, r.source_scope);
     }
   };
+  static constexpr size_t kGroupCount =
+      static_cast<uint32_t>(Group::kSizeSentinel);
+
+  TrackId InternTrackForGroup(Group group);
+
+  std::array<std::optional<TrackId>, kGroupCount> group_track_ids_;
 
   std::map<UniqueTid, TrackId> thread_tracks_;
   std::map<UniquePid, TrackId> process_tracks_;
diff --git a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
index 033d247..27df202 100644
--- a/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_descriptors.cc
@@ -24,7 +24,7 @@
 namespace trace_processor {
 namespace {
 
-std::array<FtraceMessageDescriptor, 482> descriptors{{
+std::array<FtraceMessageDescriptor, 484> descriptors{{
     {nullptr, 0, {}},
     {nullptr, 0, {}},
     {nullptr, 0, {}},
@@ -5303,6 +5303,26 @@
             {"start", ProtoSchemaType::kUint32},
         },
     },
+    {
+        "mali_mali_CSF_INTERRUPT_START",
+        3,
+        {
+            {},
+            {"kctx_tgid", ProtoSchemaType::kInt32},
+            {"kctx_id", ProtoSchemaType::kUint32},
+            {"info_val", ProtoSchemaType::kUint64},
+        },
+    },
+    {
+        "mali_mali_CSF_INTERRUPT_END",
+        3,
+        {
+            {},
+            {"kctx_tgid", ProtoSchemaType::kInt32},
+            {"kctx_id", ProtoSchemaType::kUint32},
+            {"info_val", ProtoSchemaType::kUint64},
+        },
+    },
 }};
 
 }  // namespace
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 29af4bc..ec5f264 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -25,6 +25,7 @@
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/ftrace/binder_tracker.h"
 #include "src/trace_processor/importers/ftrace/thread_state_tracker.h"
 #include "src/trace_processor/importers/ftrace/v4l2_tracker.h"
@@ -70,6 +71,7 @@
 #include "protos/perfetto/trace/ftrace/signal.pbzero.h"
 #include "protos/perfetto/trace/ftrace/skb.pbzero.h"
 #include "protos/perfetto/trace/ftrace/sock.pbzero.h"
+#include "protos/perfetto/trace/ftrace/synthetic.pbzero.h"
 #include "protos/perfetto/trace/ftrace/systrace.pbzero.h"
 #include "protos/perfetto/trace/ftrace/task.pbzero.h"
 #include "protos/perfetto/trace/ftrace/tcp.pbzero.h"
@@ -231,6 +233,10 @@
       cpu_idle_name_id_(context->storage->InternString("cpuidle")),
       suspend_resume_name_id_(
           context->storage->InternString("Suspend/Resume Latency")),
+      suspend_resume_minimal_name_id_(
+          context->storage->InternString("Suspend/Resume Minimal")),
+      suspend_resume_minimal_slice_name_id_(
+          context->storage->InternString("Suspended")),
       kfree_skb_name_id_(context->storage->InternString("Kfree Skb IP Prot")),
       ion_total_id_(context->storage->InternString("mem.ion")),
       ion_change_id_(context->storage->InternString("mem.ion_change")),
@@ -869,6 +875,10 @@
         ParseSuspendResume(ts, fld_bytes);
         break;
       }
+      case FtraceEvent::kSuspendResumeMinimalFieldNumber: {
+        ParseSuspendResumeMinimal(ts, fld_bytes);
+        break;
+      }
       case FtraceEvent::kDrmVblankEventFieldNumber:
       case FtraceEvent::kDrmVblankEventDeliveredFieldNumber:
       case FtraceEvent::kDrmSchedJobFieldNumber:
@@ -1013,6 +1023,12 @@
         mali_gpu_event_tracker_.ParseMaliGpuEvent(ts, fld.id(), pid);
         break;
       }
+      case FtraceEvent::kMaliMaliCSFINTERRUPTSTARTFieldNumber:
+      case FtraceEvent::kMaliMaliCSFINTERRUPTENDFieldNumber: {
+        mali_gpu_event_tracker_.ParseMaliGpuIrqEvent(ts, fld.id(), cpu,
+                                                     fld_bytes);
+        break;
+      }
 
       default:
         break;
@@ -1097,7 +1113,7 @@
   protos::pbzero::GenericFtraceEvent::Decoder evt(blob.data, blob.size);
   StringId event_id = context_->storage->InternString(evt.event_name());
   UniqueTid utid = context_->process_tracker->GetOrCreateThread(tid);
-  RawId id = context_->storage->mutable_raw_table()
+  RawId id = context_->storage->mutable_ftrace_event_table()
                  ->Insert({ts, event_id, cpu, utid})
                  .id;
   auto inserter = context_->args_tracker->AddArgsTo(id);
@@ -1139,7 +1155,7 @@
   const auto& message_strings = ftrace_message_strings_[ftrace_id];
   UniqueTid utid = context_->process_tracker->GetOrCreateThread(tid);
   RawId id =
-      context_->storage->mutable_raw_table()
+      context_->storage->mutable_ftrace_event_table()
           ->Insert({timestamp, message_strings.message_name_id, cpu, utid})
           .id;
   auto inserter = context_->args_tracker->AddArgsTo(id);
@@ -1428,8 +1444,8 @@
   }
 
   // Push the global counter.
-  TrackId track =
-      context_->track_tracker->InternGlobalCounterTrack(global_name_id);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kMemory, global_name_id);
   context_->event_tracker->PushCounter(timestamp,
                                        static_cast<double>(total_bytes), track);
 
@@ -1466,8 +1482,8 @@
                                 protozero::ConstBytes data) {
   protos::pbzero::IonStatFtraceEvent::Decoder ion(data.data, data.size);
   // Push the global counter.
-  TrackId track =
-      context_->track_tracker->InternGlobalCounterTrack(ion_total_id_);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kMemory, ion_total_id_);
   context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(ion.total_allocated()), track);
 
@@ -1502,8 +1518,8 @@
   protos::pbzero::DmaHeapStatFtraceEvent::Decoder dma_heap(data.data,
                                                            data.size);
   // Push the global counter.
-  TrackId track =
-      context_->track_tracker->InternGlobalCounterTrack(dma_heap_total_id_);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kMemory, dma_heap_total_id_);
   context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(dma_heap.total_allocated()), track);
 
@@ -1815,7 +1831,8 @@
                                       clock_name.data(), int(subtitle.size()),
                                       subtitle.data());
   StringId name = context_->storage->InternString(counter_name.c_str());
-  TrackId track = context_->track_tracker->InternGlobalCounterTrack(name);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kClockFrequency, name);
   context_->event_tracker->PushCounter(timestamp, static_cast<double>(rate),
                                        track);
 }
@@ -2090,8 +2107,8 @@
   if (pid == 0) {
     // Pid 0 is used to indicate the global total
     track = context_->track_tracker->InternGlobalCounterTrack(
-        gpu_mem_total_name_id_, {}, gpu_mem_total_unit_id_,
-        gpu_mem_total_global_desc_id_);
+        TrackTracker::Group::kMemory, gpu_mem_total_name_id_, {},
+        gpu_mem_total_unit_id_, gpu_mem_total_global_desc_id_);
   } else {
     // It's possible for GpuMemTotal ftrace events to be emitted by kworker
     // threads *after* process death. In this case, we simply want to discard
@@ -2129,7 +2146,8 @@
   base::StackString<255> counter_name(
       "%.*s Temperature", int(thermal_zone.size()), thermal_zone.data());
   StringId name = context_->storage->InternString(counter_name.string_view());
-  TrackId track = context_->track_tracker->InternGlobalCounterTrack(name);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kThermals, name);
   context_->event_tracker->PushCounter(timestamp, event.temp(), track);
 }
 
@@ -2140,7 +2158,8 @@
   base::StackString<255> counter_name("%.*s Cooling Device", int(type.size()),
                                       type.data());
   StringId name = context_->storage->InternString(counter_name.string_view());
-  TrackId track = context_->track_tracker->InternGlobalCounterTrack(name);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kThermals, name);
   context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(event.target()), track);
 }
@@ -2192,7 +2211,8 @@
   }
 
   // Push the global counter.
-  TrackId track = context_->track_tracker->InternGlobalCounterTrack(total_name);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kMemory, total_name);
   context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(event.total_allocated()), track);
 
@@ -2225,7 +2245,8 @@
   nic_received_bytes_[name] += event.len();
 
   uint64_t nic_received_kilobytes = nic_received_bytes_[name] / 1024;
-  TrackId track = context_->track_tracker->InternGlobalCounterTrack(name);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kNetwork, name);
   std::optional<CounterId> id = context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(nic_received_kilobytes), track);
   if (!id) {
@@ -2256,7 +2277,8 @@
   nic_transmitted_bytes_[name] += evt.len();
 
   uint64_t nic_transmitted_kilobytes = nic_transmitted_bytes_[name] / 1024;
-  TrackId track = context_->track_tracker->InternGlobalCounterTrack(name);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kNetwork, name);
   std::optional<CounterId> id = context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(nic_transmitted_kilobytes), track);
   if (!id) {
@@ -2392,14 +2414,14 @@
   // Push max freq to global counter.
   StringId max_name = context_->storage->InternString(max_counter_name.c_str());
   TrackId max_track =
-      context_->track_tracker->InternGlobalCounterTrack(max_name);
+      context_->track_tracker->InternCpuCounterTrack(max_name, evt.cpu_id());
   context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(evt.max_freq()), max_track);
 
   // Push min freq to global counter.
   StringId min_name = context_->storage->InternString(min_counter_name.c_str());
   TrackId min_track =
-      context_->track_tracker->InternGlobalCounterTrack(min_name);
+      context_->track_tracker->InternCpuCounterTrack(min_name, evt.cpu_id());
   context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(evt.min_freq()), min_track);
 }
@@ -2414,8 +2436,8 @@
   }
   num_of_kfree_skb_ip_prot += 1;
 
-  TrackId track =
-      context_->track_tracker->InternGlobalCounterTrack(kfree_skb_name_id_);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kNetwork, kfree_skb_name_id_);
   std::optional<CounterId> id = context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(num_of_kfree_skb_ip_prot), track);
   if (!id) {
@@ -2435,6 +2457,7 @@
 
   // Push the global counter.
   TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kDeviceState,
       context_->storage->InternString(
           base::StringView("cros_ec.cros_ec_sensorhub_data." +
                            std::to_string(evt.ec_sensor_num()))));
@@ -2474,8 +2497,8 @@
       clk_state = 2;
       break;
   }
-  TrackId track =
-      context_->track_tracker->InternGlobalCounterTrack(ufs_clkgating_id_);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kNetwork, ufs_clkgating_id_);
   context_->event_tracker->PushCounter(timestamp,
                                        static_cast<double>(clk_state), track);
 }
@@ -2796,8 +2819,8 @@
   uint32_t num = evt.doorbell() > 0
                      ? static_cast<uint32_t>(PERFETTO_POPCOUNT(evt.doorbell()))
                      : (evt.str_t() == 1 ? 0 : 1);
-  TrackId track =
-      context_->track_tracker->InternGlobalCounterTrack(ufs_command_count_id_);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kIo, ufs_command_count_id_);
   context_->event_tracker->PushCounter(timestamp, static_cast<double>(num),
                                        track);
 
@@ -2910,6 +2933,26 @@
   ongoing_suspend_resume_actions[current_action] = true;
 }
 
+void FtraceParser::ParseSuspendResumeMinimal(int64_t timestamp,
+                                             protozero::ConstBytes blob) {
+  protos::pbzero::SuspendResumeMinimalFtraceEvent::Decoder evt(blob.data,
+                                                               blob.size);
+  auto async_track = context_->async_track_set_tracker->InternGlobalTrackSet(
+      suspend_resume_minimal_name_id_);
+
+  if (evt.start()) {
+    TrackId start_id = context_->async_track_set_tracker->Begin(
+        async_track, static_cast<int64_t>(0));
+    context_->slice_tracker->Begin(timestamp, start_id,
+                                   suspend_resume_minimal_name_id_,
+                                   suspend_resume_minimal_slice_name_id_);
+  } else {
+    TrackId end_id = context_->async_track_set_tracker->End(
+        async_track, static_cast<int64_t>(0));
+    context_->slice_tracker->End(timestamp, end_id);
+  }
+}
+
 void FtraceParser::ParseSchedCpuUtilCfs(int64_t timestamp,
                                         protozero::ConstBytes blob) {
   protos::pbzero::SchedCpuUtilCfsFtraceEvent::Decoder evt(blob.data, blob.size);
@@ -2917,8 +2960,8 @@
   StringId util_track_name_id =
       context_->storage->InternString(util_track_name.string_view());
 
-  TrackId util_track =
-      context_->track_tracker->InternGlobalCounterTrack(util_track_name_id);
+  TrackId util_track = context_->track_tracker->InternCpuCounterTrack(
+      util_track_name_id, evt.cpu());
   context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(evt.cpu_util()), util_track);
 
@@ -2926,8 +2969,8 @@
   StringId cap_track_name_id =
       context_->storage->InternString(cap_track_name.string_view());
 
-  TrackId cap_track =
-      context_->track_tracker->InternGlobalCounterTrack(cap_track_name_id);
+  TrackId cap_track = context_->track_tracker->InternCpuCounterTrack(
+      cap_track_name_id, evt.cpu());
   context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(evt.capacity()), cap_track);
 
@@ -2936,8 +2979,8 @@
   StringId nrr_track_name_id =
       context_->storage->InternString(nrr_track_name.string_view());
 
-  TrackId nrr_track =
-      context_->track_tracker->InternGlobalCounterTrack(nrr_track_name_id);
+  TrackId nrr_track = context_->track_tracker->InternCpuCounterTrack(
+      nrr_track_name_id, evt.cpu());
   context_->event_tracker->PushCounter(
       timestamp, static_cast<double>(evt.nr_running()), nrr_track);
 }
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.h b/src/trace_processor/importers/ftrace/ftrace_parser.h
index abfb0e3..ccb40f8 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.h
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.h
@@ -216,6 +216,7 @@
   void ParseWakeSourceActivate(int64_t timestamp, protozero::ConstBytes);
   void ParseWakeSourceDeactivate(int64_t timestamp, protozero::ConstBytes);
   void ParseSuspendResume(int64_t timestamp, protozero::ConstBytes);
+  void ParseSuspendResumeMinimal(int64_t timestamp, protozero::ConstBytes);
   void ParseSchedCpuUtilCfs(int64_t timestap, protozero::ConstBytes);
 
   void ParseFuncgraphEntry(int64_t timestamp,
@@ -288,6 +289,8 @@
   const StringId gpu_freq_name_id_;
   const StringId cpu_idle_name_id_;
   const StringId suspend_resume_name_id_;
+  const StringId suspend_resume_minimal_name_id_;
+  const StringId suspend_resume_minimal_slice_name_id_;
   const StringId kfree_skb_name_id_;
   const StringId ion_total_id_;
   const StringId ion_change_id_;
diff --git a/src/trace_processor/importers/ftrace/iostat_tracker.cc b/src/trace_processor/importers/ftrace/iostat_tracker.cc
index 27e5762..d3c40d8 100644
--- a/src/trace_processor/importers/ftrace/iostat_tracker.cc
+++ b/src/trace_processor/importers/ftrace/iostat_tracker.cc
@@ -43,8 +43,8 @@
                                                    uint64_t value) {
     std::string track_name = tagPrefix + "." + std::string(counter_name);
     StringId string_id = context_->storage->InternString(track_name.c_str());
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(string_id);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kIo, string_id);
     context_->event_tracker->PushCounter(timestamp, static_cast<double>(value),
                                          track);
   };
@@ -83,8 +83,8 @@
                                                    uint64_t value) {
     std::string track_name = tagPrefix + "." + std::string(counter_name);
     StringId string_id = context_->storage->InternString(track_name.c_str());
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(string_id);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kIo, string_id);
     context_->event_tracker->PushCounter(timestamp, static_cast<double>(value),
                                          track);
   };
diff --git a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc
index c59bdcc..4064797 100644
--- a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h"
 
+#include "perfetto/ext/base/string_utils.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
 #include "protos/perfetto/trace/ftrace/mali.pbzero.h"
 #include "src/trace_processor/importers/common/async_track_set_tracker.h"
@@ -35,7 +36,11 @@
       mali_KCPU_FENCE_SIGNAL_id_(
           context->storage->InternString("mali_KCPU_FENCE_SIGNAL")),
       mali_KCPU_FENCE_WAIT_id_(
-          context->storage->InternString("mali_KCPU_FENCE_WAIT")) {}
+          context->storage->InternString("mali_KCPU_FENCE_WAIT")),
+      mali_CSF_INTERRUPT_id_(
+          context->storage->InternString("mali_CSF_INTERRUPT")),
+      mali_CSF_INTERRUPT_info_val_id_(
+          context->storage->InternString("info_val")) {}
 
 void MaliGpuEventTracker::ParseMaliGpuEvent(int64_t ts,
                                             int32_t field_id,
@@ -76,6 +81,36 @@
   }
 }
 
+void MaliGpuEventTracker::ParseMaliGpuIrqEvent(int64_t ts,
+                                               int32_t field_id,
+                                               uint32_t cpu,
+                                               protozero::ConstBytes blob) {
+  using protos::pbzero::FtraceEvent;
+
+  // Since these events are called from an interrupt context they cannot be
+  // associated to a single process or thread. Add to a custom Mali Irq track
+  // instead.
+  base::StackString<255> track_name("Mali Irq Cpu %d", cpu);
+  StringId track_name_id =
+      context_->storage->InternString(track_name.string_view());
+  TrackId track_id =
+      context_->track_tracker->InternCpuTrack(track_name_id, cpu);
+
+  switch (field_id) {
+    case FtraceEvent::kMaliMaliCSFINTERRUPTSTARTFieldNumber: {
+      ParseMaliCSFInterruptStart(ts, track_id, blob);
+      break;
+    }
+    case FtraceEvent::kMaliMaliCSFINTERRUPTENDFieldNumber: {
+      ParseMaliCSFInterruptEnd(ts, track_id, blob);
+      break;
+    }
+    default:
+      PERFETTO_DFATAL("Unexpected field id");
+      break;
+  }
+}
+
 void MaliGpuEventTracker::ParseMaliKcpuCqsSet(int64_t timestamp,
                                               TrackId track_id) {
   context_->slice_tracker->Scoped(timestamp, track_id, kNullStringId,
@@ -111,5 +146,34 @@
   context_->slice_tracker->End(timestamp, track_id, kNullStringId,
                                mali_KCPU_FENCE_WAIT_id_);
 }
+
+void MaliGpuEventTracker::ParseMaliCSFInterruptStart(
+    int64_t timestamp,
+    TrackId track_id,
+    protozero::ConstBytes blob) {
+  protos::pbzero::MaliMaliCSFINTERRUPTSTARTFtraceEvent::Decoder evt(blob.data,
+                                                                    blob.size);
+  auto args_inserter = [this, &evt](ArgsTracker::BoundInserter* inserter) {
+    inserter->AddArg(mali_CSF_INTERRUPT_info_val_id_,
+                     Variadic::UnsignedInteger(evt.info_val()));
+  };
+
+  context_->slice_tracker->Begin(timestamp, track_id, kNullStringId,
+                                 mali_CSF_INTERRUPT_id_, args_inserter);
+}
+
+void MaliGpuEventTracker::ParseMaliCSFInterruptEnd(int64_t timestamp,
+                                                   TrackId track_id,
+                                                   protozero::ConstBytes blob) {
+  protos::pbzero::MaliMaliCSFINTERRUPTSTARTFtraceEvent::Decoder evt(blob.data,
+                                                                    blob.size);
+  auto args_inserter = [this, &evt](ArgsTracker::BoundInserter* inserter) {
+    inserter->AddArg(mali_CSF_INTERRUPT_info_val_id_,
+                     Variadic::UnsignedInteger(evt.info_val()));
+  };
+
+  context_->slice_tracker->End(timestamp, track_id, kNullStringId,
+                               mali_CSF_INTERRUPT_id_, args_inserter);
+}
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h
index 9f74c63..9342666 100644
--- a/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h
+++ b/src/trace_processor/importers/ftrace/mali_gpu_event_tracker.h
@@ -18,6 +18,7 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_FTRACE_MALI_GPU_EVENT_TRACKER_H_
 
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/util/descriptors.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -28,6 +29,10 @@
  public:
   explicit MaliGpuEventTracker(TraceProcessorContext*);
   void ParseMaliGpuEvent(int64_t timestamp, int32_t field_id, uint32_t pid);
+  void ParseMaliGpuIrqEvent(int64_t timestamp,
+                            int32_t field_id,
+                            uint32_t cpu,
+                            protozero::ConstBytes blob);
 
  private:
   TraceProcessorContext* context_;
@@ -35,12 +40,20 @@
   StringId mali_KCPU_CQS_WAIT_id_;
   StringId mali_KCPU_FENCE_SIGNAL_id_;
   StringId mali_KCPU_FENCE_WAIT_id_;
+  StringId mali_CSF_INTERRUPT_id_;
+  StringId mali_CSF_INTERRUPT_info_val_id_;
   void ParseMaliKcpuFenceSignal(int64_t timestamp, TrackId track_id);
   void ParseMaliKcpuFenceWaitStart(int64_t timestamp, TrackId track_id);
   void ParseMaliKcpuFenceWaitEnd(int64_t timestamp, TrackId track_id);
   void ParseMaliKcpuCqsSet(int64_t timestamp, TrackId track_id);
   void ParseMaliKcpuCqsWaitStart(int64_t timestamp, TrackId track_id);
   void ParseMaliKcpuCqsWaitEnd(int64_t timestamp, TrackId track_id);
+  void ParseMaliCSFInterruptStart(int64_t timestamp,
+                                  TrackId track_id,
+                                  protozero::ConstBytes blob);
+  void ParseMaliCSFInterruptEnd(int64_t timestamp,
+                                TrackId track_id,
+                                protozero::ConstBytes blob);
 };
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/importers/ftrace/sched_event_tracker.cc b/src/trace_processor/importers/ftrace/sched_event_tracker.cc
index f67e571..68fe3b7 100644
--- a/src/trace_processor/importers/ftrace/sched_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker.cc
@@ -240,7 +240,7 @@
 
   if (PERFETTO_LIKELY(context_->config.ingest_ftrace_in_raw_table)) {
     // Add an entry to the raw table.
-    RawId id = context_->storage->mutable_raw_table()
+    RawId id = context_->storage->mutable_ftrace_event_table()
                    ->Insert({ts, sched_waking_id_, cpu, curr_utid})
                    .id;
 
@@ -277,7 +277,7 @@
   if (PERFETTO_LIKELY(context_->config.ingest_ftrace_in_raw_table)) {
     // Push the raw event - this is done as the raw ftrace event codepath does
     // not insert sched_switch.
-    RawId id = context_->storage->mutable_raw_table()
+    RawId id = context_->storage->mutable_ftrace_event_table()
                    ->Insert({ts, sched_switch_id_, cpu, prev_utid})
                    .id;
 
diff --git a/src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc b/src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc
index d4d09a2..9813311 100644
--- a/src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc
+++ b/src/trace_processor/importers/ftrace/virtio_gpu_tracker.cc
@@ -160,8 +160,8 @@
 
 void VirtioGpuTracker::VirtioGpuQueue::HandleNumFree(int64_t timestamp,
                                                      uint32_t num_free) {
-  TrackId track =
-      context_->track_tracker->InternGlobalCounterTrack(num_free_id_);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kVirtio, num_free_id_);
   context_->event_tracker->PushCounter(timestamp, static_cast<double>(num_free),
                                        track);
 }
@@ -201,10 +201,10 @@
 
   int64_t duration = timestamp - *start_timestamp;
 
-  TrackId track =
-      context_->track_tracker->InternGlobalCounterTrack(latency_id_);
-  context_->event_tracker->PushCounter(timestamp,
-                                       static_cast<double>(duration), track);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kVirtio, latency_id_);
+  context_->event_tracker->PushCounter(timestamp, static_cast<double>(duration),
+                                       track);
 
   start_timestamps_.Erase(seqno);
 }
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
index 8acc450..e61a137 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_parser_unittest.cc
@@ -333,6 +333,44 @@
   EXPECT_EQ(context_.storage->stats()[stats::fuchsia_invalid_event].value, 0);
 }
 
+TEST_F(FuchsiaTraceParserTest, BooleanArguments) {
+  // Inline name of 8 bytes
+  uint64_t name_ref = uint64_t{0x8008} << 48;
+  // Inline category of 8 bytes
+  uint64_t category_ref = uint64_t{0x8008} << 32;
+  // Inline threadref
+  uint64_t threadref = uint64_t{0};
+  // 2 arguments
+  uint64_t argument_count = uint64_t{2} << 20;
+  // Instant Event
+  uint64_t event_type = 0 << 16;
+  uint64_t size = 8 << 4;
+  uint64_t record_type = 4;
+
+  auto header = name_ref | category_ref | threadref | event_type |
+                argument_count | size | record_type;
+  push_word(header);
+  // Timestamp
+  push_word(0xAAAAAAAAAAAAAAAA);
+  // Pid + tid
+  push_word(0xBBBBBBBBBBBBBBBB);
+  push_word(0xCCCCCCCCCCCCCCCC);
+  // Inline Category
+  push_word(0xDDDDDDDDDDDDDDDD);
+  // Inline Name
+  push_word(0xEEEEEEEEEEEEEEEE);
+  // Boolean argument true
+  push_word(0x0000'0001'8008'0029);
+  // 8 byte arg name stream
+  push_word(0x0000'0000'0000'0000);
+  // Boolean argument false
+  push_word(0x0000'0000'8008'002A);
+  // 8 byte arg name stream
+  push_word(0x0000'0000'0000'0000);
+  EXPECT_TRUE(Tokenize().ok());
+  EXPECT_EQ(context_.storage->stats()[stats::fuchsia_invalid_event].value, 0);
+}
+
 TEST_F(FuchsiaTraceParserTest, FxtWithProtos) {
   // Serialize some protos to bytes
   protozero::HeapBuffered<protos::pbzero::Trace> protos;
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
index 0143060..0b18a77 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_parser.cc
@@ -55,6 +55,7 @@
 constexpr uint32_t kString = 6;
 constexpr uint32_t kPointer = 7;
 constexpr uint32_t kKoid = 8;
+constexpr uint32_t kBool = 9;
 
 }  // namespace
 
@@ -169,6 +170,11 @@
         arg.value = fuchsia_trace_utils::ArgValue::Koid(value);
         break;
       }
+      case kBool: {
+        arg.value = fuchsia_trace_utils::ArgValue::Bool(
+            fuchsia_trace_utils::ReadField<bool>(arg_header, 32, 63));
+        break;
+      }
       default:
         arg.value = fuchsia_trace_utils::ArgValue::Unknown();
         break;
@@ -325,6 +331,7 @@
               case fuchsia_trace_utils::ArgValue::kString:
               case fuchsia_trace_utils::ArgValue::kPointer:
               case fuchsia_trace_utils::ArgValue::kKoid:
+              case fuchsia_trace_utils::ArgValue::kBool:
               case fuchsia_trace_utils::ArgValue::kUnknown:
                 context_->storage->IncrementStats(
                     stats::fuchsia_non_numeric_counters);
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.cc
index 47de0f8..48dfe2e 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.cc
@@ -83,6 +83,8 @@
       return Variadic::Pointer(pointer_);
     case ArgType::kKoid:
       return Variadic::Integer(static_cast<int64_t>(koid_));
+    case ArgType::kBool:
+      return Variadic::Boolean(bool_);
     case ArgType::kUnknown:
       return Variadic::String(storage->InternString("unknown"));
   }
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h b/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h
index f4ae231..d4a3efa 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_utils.h
@@ -52,6 +52,7 @@
     kString,
     kPointer,
     kKoid,
+    kBool,
     kUnknown,
   };
 
@@ -118,6 +119,13 @@
     return v;
   }
 
+  static ArgValue Bool(bool value) {
+    ArgValue v;
+    v.type_ = ArgType::kBool;
+    v.bool_ = value;
+    return v;
+  }
+
   static ArgValue Unknown() {
     ArgValue v;
     v.type_ = ArgType::kUnknown;
@@ -167,6 +175,11 @@
     return koid_;
   }
 
+  uint64_t Bool() const {
+    PERFETTO_DCHECK(type_ == ArgType::kBool);
+    return bool_;
+  }
+
   Variadic ToStorageVariadic(TraceStorage*) const;
 
  private:
@@ -180,6 +193,7 @@
     StringId string_;
     uint64_t pointer_;
     uint64_t koid_;
+    bool bool_;
   };
 };
 
diff --git a/src/trace_processor/importers/proto/android_camera_event_module.h b/src/trace_processor/importers/proto/android_camera_event_module.h
index 88e7476..1d55081 100644
--- a/src/trace_processor/importers/proto/android_camera_event_module.h
+++ b/src/trace_processor/importers/proto/android_camera_event_module.h
@@ -24,7 +24,7 @@
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 #include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
-#include "src/trace_processor/tables/slice_tables.h"
+#include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
diff --git a/src/trace_processor/importers/proto/android_probes_module.cc b/src/trace_processor/importers/proto/android_probes_module.cc
index e1feb51..fece007 100644
--- a/src/trace_processor/importers/proto/android_probes_module.cc
+++ b/src/trace_processor/importers/proto/android_probes_module.cc
@@ -155,7 +155,8 @@
     StringId counter_name_id =
         context_->storage->InternString(counter_name.string_view());
     TrackId track = context_->track_tracker->InternGlobalCounterTrack(
-        counter_name_id, [this, &desc](ArgsTracker::BoundInserter& inserter) {
+        TrackTracker::Group::kPower, counter_name_id,
+        [this, &desc](ArgsTracker::BoundInserter& inserter) {
           StringId raw_name = context_->storage->InternString(desc.rail_name());
           inserter.AddArg(power_rail_raw_name_id_, Variadic::String(raw_name));
 
@@ -180,7 +181,9 @@
             : packet_timestamp;
 
     protozero::HeapBuffered<protos::pbzero::TracePacket> data_packet;
-    data_packet->set_timestamp(static_cast<uint64_t>(actual_ts));
+    // Keep the original timestamp to later extract as an arg; the sorter does
+    // not read this.
+    data_packet->set_timestamp(static_cast<uint64_t>(packet_timestamp));
 
     auto* energy = data_packet->set_power_rails()->add_energy_data();
     energy->set_energy(data.energy());
@@ -206,7 +209,7 @@
       parser_.ParseBatteryCounters(ts, decoder.battery());
       return;
     case TracePacket::kPowerRailsFieldNumber:
-      parser_.ParsePowerRails(ts, decoder.power_rails());
+      parser_.ParsePowerRails(ts, decoder.timestamp(), decoder.power_rails());
       return;
     case TracePacket::kAndroidEnergyEstimationBreakdownFieldNumber:
       parser_.ParseEnergyBreakdown(
diff --git a/src/trace_processor/importers/proto/android_probes_parser.cc b/src/trace_processor/importers/proto/android_probes_parser.cc
index 754f30a..b165a2b 100644
--- a/src/trace_processor/importers/proto/android_probes_parser.cc
+++ b/src/trace_processor/importers/proto/android_probes_parser.cc
@@ -62,7 +62,8 @@
       screen_state_id_(context->storage->InternString("ScreenState")),
       device_state_id_(context->storage->InternString("DeviceStateChanged")),
       battery_status_id_(context->storage->InternString("BatteryStatus")),
-      plug_type_id_(context->storage->InternString("PlugType")) {}
+      plug_type_id_(context->storage->InternString("PlugType")),
+      rail_packet_timestamp_id_(context->storage->InternString("packet_ts")) {}
 
 void AndroidProbesParser::ParseBatteryCounters(int64_t ts, ConstBytes blob) {
   protos::pbzero::BatteryCounters::Decoder evt(blob.data, blob.size);
@@ -82,14 +83,14 @@
         std::string("batt.").append(batt_name).append(".current.avg_ua")));
   }
   if (evt.has_charge_counter_uah()) {
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(batt_charge_id);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kPower, batt_charge_id);
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(evt.charge_counter_uah()), track);
   } else if (evt.has_energy_counter_uwh() && evt.has_voltage_uv()) {
     // Calculate charge counter from energy counter and voltage.
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(batt_charge_id);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kPower, batt_charge_id);
     auto energy = evt.energy_counter_uwh();
     auto voltage = evt.voltage_uv();
     if (voltage > 0) {
@@ -99,26 +100,28 @@
   }
 
   if (evt.has_capacity_percent()) {
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(batt_capacity_id);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kPower, batt_capacity_id);
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(evt.capacity_percent()), track);
   }
   if (evt.has_current_ua()) {
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(batt_current_id);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kPower, batt_current_id);
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(evt.current_ua()), track);
   }
   if (evt.has_current_avg_ua()) {
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(batt_current_avg_id);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kPower, batt_current_avg_id);
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(evt.current_avg_ua()), track);
   }
 }
 
-void AndroidProbesParser::ParsePowerRails(int64_t ts, ConstBytes blob) {
+void AndroidProbesParser::ParsePowerRails(int64_t ts,
+                                          uint64_t trace_packet_ts,
+                                          ConstBytes blob) {
   protos::pbzero::PowerRails::Decoder evt(blob.data, blob.size);
 
   // Descriptors should have been processed at tokenization time.
@@ -134,12 +137,16 @@
   auto opt_track = tracker->GetPowerRailTrack(desc.index());
   if (opt_track.has_value()) {
     // The tokenization makes sure that this field is always present and
-    // is equal to the packet's timestamp (as the packet was forged in
-    // the tokenizer).
+    // is equal to the packet's timestamp that was passed to us via the sorter.
     PERFETTO_DCHECK(desc.has_timestamp_ms());
     PERFETTO_DCHECK(ts / 1000000 == static_cast<int64_t>(desc.timestamp_ms()));
-    context_->event_tracker->PushCounter(ts, static_cast<double>(desc.energy()),
-                                         *opt_track);
+    auto maybe_counter_id = context_->event_tracker->PushCounter(
+        ts, static_cast<double>(desc.energy()), *opt_track);
+    if (maybe_counter_id) {
+      context_->args_tracker->AddArgsTo(*maybe_counter_id)
+          .AddArg(rail_packet_timestamp_id_,
+                  Variadic::UnsignedInteger(trace_packet_ts));
+    }
   } else {
     context_->storage->IncrementStats(stats::power_rail_unknown_index);
   }
@@ -219,7 +226,7 @@
     }
 
     TrackId track = context_->track_tracker->InternGlobalCounterTrack(
-        entity_state->overall_name);
+        TrackTracker::Group::kPower, entity_state->overall_name);
     context_->event_tracker->PushCounter(
         ts, double(residency.total_time_in_state_ms()), track);
   }
@@ -396,8 +403,8 @@
                                                    ConstBytes blob) {
   protos::pbzero::InitialDisplayState::Decoder state(blob.data, blob.size);
 
-  TrackId track =
-      context_->track_tracker->InternGlobalCounterTrack(screen_state_id_);
+  TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+      TrackTracker::Group::kDeviceState, screen_state_id_);
   context_->event_tracker->PushCounter(ts, state.display_state(), track);
 }
 
@@ -427,8 +434,8 @@
       std::optional<int32_t> state =
           base::StringToInt32(kv.value().ToStdString());
       if (state) {
-        TrackId track =
-            context_->track_tracker->InternGlobalCounterTrack(name_id);
+        TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+            TrackTracker::Group::kNetwork, name_id);
         context_->event_tracker->PushCounter(ts, *state, track);
       }
     } else if (name == "debug.tracing.screen_state") {
@@ -442,8 +449,8 @@
       std::optional<int32_t> state =
           base::StringToInt32(kv.value().ToStdString());
       if (state) {
-        TrackId track =
-            context_->track_tracker->InternGlobalCounterTrack(*mapped_name_id);
+        TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+            TrackTracker::Group::kDeviceState, *mapped_name_id);
         context_->event_tracker->PushCounter(ts, *state, track);
       }
     }
diff --git a/src/trace_processor/importers/proto/android_probes_parser.h b/src/trace_processor/importers/proto/android_probes_parser.h
index 7ea91a1..06b1019 100644
--- a/src/trace_processor/importers/proto/android_probes_parser.h
+++ b/src/trace_processor/importers/proto/android_probes_parser.h
@@ -34,7 +34,7 @@
   explicit AndroidProbesParser(TraceProcessorContext*);
 
   void ParseBatteryCounters(int64_t ts, ConstBytes);
-  void ParsePowerRails(int64_t ts, ConstBytes);
+  void ParsePowerRails(int64_t ts, uint64_t trace_packet_ts, ConstBytes);
   void ParseEnergyBreakdown(int64_t ts, ConstBytes);
   void ParseEntityStateResidency(int64_t ts, ConstBytes);
   void ParseAndroidLogPacket(ConstBytes);
@@ -56,6 +56,7 @@
   const StringId device_state_id_;
   const StringId battery_status_id_;
   const StringId plug_type_id_;
+  const StringId rail_packet_timestamp_id_;
 };
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/importers/proto/atoms.descriptor b/src/trace_processor/importers/proto/atoms.descriptor
index daeacb7..2d4c191 100644
--- a/src/trace_processor/importers/proto/atoms.descriptor
+++ b/src/trace_processor/importers/proto/atoms.descriptor
Binary files differ
diff --git a/src/trace_processor/importers/proto/chrome_system_probes_parser.h b/src/trace_processor/importers/proto/chrome_system_probes_parser.h
index 8c65fd4..b011fe5 100644
--- a/src/trace_processor/importers/proto/chrome_system_probes_parser.h
+++ b/src/trace_processor/importers/proto/chrome_system_probes_parser.h
@@ -43,7 +43,7 @@
   // Maps a proto field number for memcounters in ProcessStats::Process to
   // their StringId. Keep kProcStatsProcessSize equal to 1 + max proto field
   // id of ProcessStats::Process. Also update SystemProbesParser.
-  static constexpr size_t kProcStatsProcessSize = 15;
+  static constexpr size_t kProcStatsProcessSize = 21;
   std::array<StringId, kProcStatsProcessSize> proc_stats_process_names_{};
 };
 
diff --git a/src/trace_processor/importers/proto/gpu_event_parser.cc b/src/trace_processor/importers/proto/gpu_event_parser.cc
index 56bc165..9022cbb 100644
--- a/src/trace_processor/importers/proto/gpu_event_parser.cc
+++ b/src/trace_processor/importers/proto/gpu_event_parser.cc
@@ -735,8 +735,8 @@
   if (pid == 0) {
     // Pid 0 is used to indicate the global total
     track = context_->track_tracker->InternGlobalCounterTrack(
-        gpu_mem_total_name_id_, {}, gpu_mem_total_unit_id_,
-        gpu_mem_total_global_desc_id_);
+        TrackTracker::Group::kMemory, gpu_mem_total_name_id_, {},
+        gpu_mem_total_unit_id_, gpu_mem_total_global_desc_id_);
   } else {
     // Process emitting the packet can be different from the pid in the event.
     UniqueTid utid = context_->process_tracker->UpdateThread(pid, pid);
diff --git a/src/trace_processor/importers/proto/proto_trace_parser.cc b/src/trace_processor/importers/proto/proto_trace_parser.cc
index fa1ffd6..b18bdea 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser.cc
@@ -357,7 +357,7 @@
     // args in arrays.
     std::stable_sort(interned.begin(), interned.end(),
                      [](const Arg& a, const Arg& b) {
-                       return a.first.raw_id() < b.second.raw_id();
+                       return a.first.raw_id() < b.first.raw_id();
                      });
 
     // Compute the correct key for each arg, possibly adding an index to
@@ -373,20 +373,16 @@
         inserter->AddArg(key, Variadic::String(it->second));
       } else {
         constexpr size_t kMaxIndexSize = 20;
-        base::StringView key_str = context_->storage->GetString(key);
+        NullTermStringView key_str = context_->storage->GetString(key);
         if (key_str.size() >= sizeof(buffer) - kMaxIndexSize) {
           PERFETTO_DLOG("Ignoring arg with unreasonbly large size");
           continue;
         }
 
-        base::StringWriter writer(buffer, sizeof(buffer));
-        writer.AppendString(key_str);
-        writer.AppendChar('[');
-        writer.AppendUnsignedInt(current_idx);
-        writer.AppendChar(']');
-
+        base::StackString<2048> array_key("%s[%u]", key_str.c_str(),
+                                          current_idx);
         StringId new_key =
-            context_->storage->InternString(writer.GetStringView());
+            context_->storage->InternString(array_key.string_view());
         inserter->AddArg(key, new_key, Variadic::String(it->second));
 
         current_idx = key == next_key ? current_idx + 1 : 0;
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 2444215..97779d4 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -647,11 +647,11 @@
   meminfo->set_value(value);
 
   EXPECT_CALL(*event_, PushCounter(static_cast<int64_t>(ts),
-                                   DoubleEq(value * 1024.0), TrackId{0u}));
+                                   DoubleEq(value * 1024.0), TrackId{1u}));
   Tokenize();
   context_.sorter->ExtractEventsForced();
 
-  EXPECT_EQ(context_.storage->track_table().row_count(), 1u);
+  EXPECT_EQ(context_.storage->track_table().row_count(), 2u);
 }
 
 TEST_F(ProtoTraceParserTest, LoadVmStats) {
@@ -665,11 +665,11 @@
   meminfo->set_value(value);
 
   EXPECT_CALL(*event_, PushCounter(static_cast<int64_t>(ts), DoubleEq(value),
-                                   TrackId{0u}));
+                                   TrackId{1u}));
   Tokenize();
   context_.sorter->ExtractEventsForced();
 
-  EXPECT_EQ(context_.storage->track_table().row_count(), 1u);
+  EXPECT_EQ(context_.storage->track_table().row_count(), 2u);
 }
 
 TEST_F(ProtoTraceParserTest, LoadProcessPacket) {
diff --git a/src/trace_processor/importers/proto/statsd_module.h b/src/trace_processor/importers/proto/statsd_module.h
index 999a339..430b7a2 100644
--- a/src/trace_processor/importers/proto/statsd_module.h
+++ b/src/trace_processor/importers/proto/statsd_module.h
@@ -26,7 +26,7 @@
 #include "src/trace_processor/importers/common/trace_parser.h"
 #include "src/trace_processor/importers/proto/proto_importer_module.h"
 #include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/slice_tables.h"
+#include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/descriptors.h"
diff --git a/src/trace_processor/importers/proto/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index da175fa..da6a2d2 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.cc
+++ b/src/trace_processor/importers/proto/system_probes_parser.cc
@@ -153,6 +153,16 @@
       context->storage->InternString("mem.rss.watermark");
   proc_stats_process_names_[ProcessStats::Process::kOomScoreAdjFieldNumber] =
       oom_score_adj_id_;
+  proc_stats_process_names_[ProcessStats::Process::kSmrRssKbFieldNumber] =
+      context->storage->InternString("mem.smaps.rss");
+  proc_stats_process_names_[ProcessStats::Process::kSmrPssKbFieldNumber] =
+      context->storage->InternString("mem.smaps.pss");
+  proc_stats_process_names_[ProcessStats::Process::kSmrPssAnonKbFieldNumber] =
+      context->storage->InternString("mem.smaps.pss.anon");
+  proc_stats_process_names_[ProcessStats::Process::kSmrPssFileKbFieldNumber] =
+      context->storage->InternString("mem.smaps.pss.file");
+  proc_stats_process_names_[ProcessStats::Process::kSmrPssShmemKbFieldNumber] =
+      context->storage->InternString("mem.smaps.pss.shmem");
 }
 
 void SystemProbesParser::ParseDiskStats(int64_t ts, ConstBytes blob) {
@@ -170,8 +180,8 @@
     base::StackString<512> track_name("%s.%s", tag_prefix.c_str(),
                                       counter_name);
     StringId string_id = context_->storage->InternString(track_name.c_str());
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(string_id);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kIo, string_id);
     context_->event_tracker->PushCounter(ts, value, track);
   };
 
@@ -244,7 +254,7 @@
     }
     // /proc/meminfo counters are in kB, convert to bytes
     TrackId track = context_->track_tracker->InternGlobalCounterTrack(
-        meminfo_strs_id_[key]);
+        TrackTracker::Group::kMemory, meminfo_strs_id_[key]);
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(mi.value()) * 1024., track);
   }
@@ -259,7 +269,8 @@
         "%.*s %.*s", int(key.size()), key.data(), int(devfreq_subtitle.size()),
         devfreq_subtitle.data());
     StringId name = context_->storage->InternString(counter_name.string_view());
-    TrackId track = context_->track_tracker->InternGlobalCounterTrack(name);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kClockFrequency, name);
     context_->event_tracker->PushCounter(ts, static_cast<double>(vm.value()),
                                          track);
   }
@@ -279,8 +290,8 @@
       context_->storage->IncrementStats(stats::vmstat_unknown_keys);
       continue;
     }
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(vmstat_strs_id_[key]);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kMemory, vmstat_strs_id_[key]);
     context_->event_tracker->PushCounter(ts, static_cast<double>(vm.value()),
                                          track);
   }
@@ -348,22 +359,22 @@
   }
 
   if (sys_stats.has_num_forks()) {
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(num_forks_name_id_);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kDeviceState, num_forks_name_id_);
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(sys_stats.num_forks()), track);
   }
 
   if (sys_stats.has_num_irq_total()) {
     TrackId track = context_->track_tracker->InternGlobalCounterTrack(
-        num_irq_total_name_id_);
+        TrackTracker::Group::kDeviceState, num_irq_total_name_id_);
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(sys_stats.num_irq_total()), track);
   }
 
   if (sys_stats.has_num_softirq_total()) {
     TrackId track = context_->track_tracker->InternGlobalCounterTrack(
-        num_softirq_total_name_id_);
+        TrackTracker::Group::kDeviceState, num_softirq_total_name_id_);
     context_->event_tracker->PushCounter(
         ts, static_cast<double>(sys_stats.num_softirq_total()), track);
   }
@@ -380,7 +391,8 @@
                                           node.c_str(), zone.c_str(), size_kb);
       StringId name =
           context_->storage->InternString(counter_name.string_view());
-      TrackId track = context_->track_tracker->InternGlobalCounterTrack(name);
+      TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+          TrackTracker::Group::kMemory, name);
       context_->event_tracker->PushCounter(ts, static_cast<double>(*order_it),
                                            track);
       order++;
diff --git a/src/trace_processor/importers/proto/system_probes_parser.h b/src/trace_processor/importers/proto/system_probes_parser.h
index 77721d5..c1576a5 100644
--- a/src/trace_processor/importers/proto/system_probes_parser.h
+++ b/src/trace_processor/importers/proto/system_probes_parser.h
@@ -72,7 +72,7 @@
   // their StringId. Keep kProcStatsProcessSize equal to 1 + max proto field
   // id of ProcessStats::Process. Also update the value in
   // ChromeSystemProbesParser.
-  static constexpr size_t kProcStatsProcessSize = 15;
+  static constexpr size_t kProcStatsProcessSize = 21;
   std::array<StringId, kProcStatsProcessSize> proc_stats_process_names_{};
 
   uint64_t ms_per_tick_ = 0;
diff --git a/src/trace_processor/importers/proto/track_event_tracker.cc b/src/trace_processor/importers/proto/track_event_tracker.cc
index 7591028..c656564 100644
--- a/src/trace_processor/importers/proto/track_event_tracker.cc
+++ b/src/trace_processor/importers/proto/track_event_tracker.cc
@@ -24,11 +24,6 @@
 namespace perfetto {
 namespace trace_processor {
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-// static
-constexpr uint64_t TrackEventTracker::kDefaultDescriptorTrackUuid;
-#endif
-
 TrackEventTracker::TrackEventTracker(TraceProcessorContext* context)
     : source_key_(context->storage->InternString("source")),
       source_id_key_(context->storage->InternString("source_id")),
diff --git a/src/trace_processor/importers/systrace/systrace_line_parser.cc b/src/trace_processor/importers/systrace/systrace_line_parser.cc
index 958cb7e..c0851d7 100644
--- a/src/trace_processor/importers/systrace/systrace_line_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_line_parser.cc
@@ -216,8 +216,8 @@
     std::string clock_name_str = args["name"] + subtitle;
     StringId clock_name =
         context_->storage->InternString(base::StringView(clock_name_str));
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(clock_name);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kClockFrequency, clock_name);
     context_->event_tracker->PushCounter(line.ts, rate.value(), track);
   } else if (line.event_name == "workqueue_execute_start") {
     auto split = base::SplitString(line.args_str, "function ");
@@ -232,8 +232,8 @@
     std::string thermal_zone = args["thermal_zone"] + " Temperature";
     StringId track_name =
         context_->storage->InternString(base::StringView(thermal_zone));
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(track_name);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kThermals, track_name);
     auto temp = base::StringToInt32(args["temp"]);
     if (!temp.has_value()) {
       return util::Status("Could not convert temp");
@@ -243,8 +243,8 @@
     std::string type = args["type"] + " Cooling Device";
     StringId track_name =
         context_->storage->InternString(base::StringView(type));
-    TrackId track =
-        context_->track_tracker->InternGlobalCounterTrack(track_name);
+    TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+        TrackTracker::Group::kThermals, track_name);
     auto target = base::StringToDouble(args["target"]);
     if (!target.has_value()) {
       return util::Status("Could not convert target");
diff --git a/src/trace_processor/importers/systrace/systrace_parser.cc b/src/trace_processor/importers/systrace/systrace_parser.cc
index ac16506..b06bb71 100644
--- a/src/trace_processor/importers/systrace/systrace_parser.cc
+++ b/src/trace_processor/importers/systrace/systrace_parser.cc
@@ -277,16 +277,16 @@
         // once the UI has support for displaying instants.
       } else if (point.name == "ScreenState") {
         // Promote ScreenState to its own top level counter.
-        TrackId track =
-            context_->track_tracker->InternGlobalCounterTrack(screen_state_id_);
+        TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+            TrackTracker::Group::kDeviceState, screen_state_id_);
         context_->event_tracker->PushCounter(
             ts, static_cast<double>(point.int_value), track);
         return;
       } else if (point.name.StartsWith("battery_stats.")) {
         // Promote battery_stats conters to global tracks.
         StringId name_id = context_->storage->InternString(point.name);
-        TrackId track =
-            context_->track_tracker->InternGlobalCounterTrack(name_id);
+        TrackId track = context_->track_tracker->InternGlobalCounterTrack(
+            TrackTracker::Group::kPower, name_id);
         context_->event_tracker->PushCounter(
             ts, static_cast<double>(point.int_value), track);
         return;
diff --git a/src/trace_processor/metrics/metrics.h b/src/trace_processor/metrics/metrics.h
index 16e004c..2691e91 100644
--- a/src/trace_processor/metrics/metrics.h
+++ b/src/trace_processor/metrics/metrics.h
@@ -27,7 +27,7 @@
 #include "perfetto/protozero/message.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
 #include "perfetto/trace_processor/trace_processor.h"
-#include "src/trace_processor/prelude/functions/register_function.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
 #include "src/trace_processor/util/descriptors.h"
 
 #include "protos/perfetto/trace_processor/metrics_impl.pbzero.h"
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index bf54d44..a566661 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -82,6 +82,7 @@
     "java_heap_histogram.sql",
     "java_heap_stats.sql",
     "mem_stats_priority_breakdown.sql",
+    "network_activity_template.sql",
     "p_state.sql",
     "power_drain_in_watts.sql",
     "power_profile_data.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_batt.sql b/src/trace_processor/metrics/sql/android/android_batt.sql
index 7dde099..b88d7ed 100644
--- a/src/trace_processor/metrics/sql/android/android_batt.sql
+++ b/src/trace_processor/metrics/sql/android/android_batt.sql
@@ -49,8 +49,16 @@
 )
 GROUP BY group_id;
 
+DROP VIEW IF EXISTS suspend_slice_from_minimal;
+CREATE VIEW suspend_slice_from_minimal AS
+SELECT ts, dur
+FROM track t JOIN slice s ON s.track_id = t.id
+WHERE t.name = 'Suspend/Resume Minimal';
+
 DROP TABLE IF EXISTS suspend_slice_;
 CREATE TABLE suspend_slice_ AS
+SELECT ts, dur FROM suspend_slice_from_minimal
+UNION ALL
 SELECT
   ts,
   dur
@@ -62,7 +70,10 @@
 WHERE
   track.name = 'Suspend/Resume Latency'
   AND (slice.name = 'syscore_resume(0)' OR slice.name = 'timekeeping_freeze(0)')
-  AND dur != -1;
+  AND dur != -1
+  AND NOT EXISTS(SELECT * FROM suspend_slice_from_minimal);
+
+DROP VIEW suspend_slice_from_minimal;
 
 SELECT RUN_METRIC('android/counter_span_view_merged.sql',
   'table_name', 'screen_state',
diff --git a/src/trace_processor/metrics/sql/android/android_binder.sql b/src/trace_processor/metrics/sql/android/android_binder.sql
index 1d15050..f59f5d9 100644
--- a/src/trace_processor/metrics/sql/android/android_binder.sql
+++ b/src/trace_processor/metrics/sql/android/android_binder.sql
@@ -45,11 +45,13 @@
         'client_ts', client_ts,
         'client_dur', client_dur,
         'client_tid', client_tid,
+        'client_pid', client_pid,
         'server_process', server_process,
         'server_thread', server_thread,
         'server_ts', server_ts,
         'server_dur', server_dur,
         'server_tid', server_tid,
+        'server_pid', server_pid,
         'thread_states', (
           SELECT RepeatedField(
             AndroidBinderMetric_ThreadStateBreakdown(
diff --git a/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql b/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql
index 7c56db2..3538f7d 100644
--- a/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql
+++ b/src/trace_processor/metrics/sql/android/android_blocking_calls_cuj_metric.sql
@@ -20,6 +20,7 @@
 SELECT RUN_METRIC('android/android_jank_cuj.sql');
 
 SELECT IMPORT('android.slices');
+SELECT IMPORT('android.binder');
 
 -- Jank "J<*>" and latency "L<*>" cujs are put together in android_cujs table.
 -- They are computed separately as latency ones are slightly different, don't
@@ -75,6 +76,24 @@
 FROM all_cujs;
 
 
+DROP TABLE IF EXISTS relevant_binder_calls_with_names;
+CREATE TABLE relevant_binder_calls_with_names AS
+SELECT DISTINCT
+    tx.aidl_name AS name,
+    tx.client_ts AS ts,
+    s.track_id,
+    tx.client_dur AS dur,
+    s.id,
+    tx.client_process as process_name,
+    tx.client_utid as utid,
+    tx.client_upid as upid
+FROM android_sync_binder_metrics_by_txn AS tx
+         JOIN slice AS s ON s.id = tx.binder_txn_id
+        -- Keeps only slices in cuj processes.
+         JOIN android_cujs ON tx.client_upid = android_cujs.upid
+WHERE is_main_thread AND aidl_name IS NOT NULL;
+
+
 DROP TABLE IF EXISTS android_blocking_calls_cuj_calls;
 CREATE TABLE android_blocking_calls_cuj_calls AS
 WITH all_main_thread_relevant_slices AS (
@@ -108,6 +127,17 @@
             OR s.name GLOB 'relayoutWindow*'
             OR s.name GLOB 'ImageDecoder#decode*'
         )
+    UNION ALL
+    SELECT
+        name,
+        ts,
+        track_id,
+        dur,
+        id,
+        process_name,
+        utid,
+        upid
+    FROM relevant_binder_calls_with_names
 ),
 -- Now we have:
 --  (1) a list of slices from the main thread of each process
diff --git a/src/trace_processor/metrics/sql/android/android_jank_cuj.sql b/src/trace_processor/metrics/sql/android/android_jank_cuj.sql
index bf7ce9f..299e830 100644
--- a/src/trace_processor/metrics/sql/android/android_jank_cuj.sql
+++ b/src/trace_processor/metrics/sql/android/android_jank_cuj.sql
@@ -102,6 +102,8 @@
               'missed_app_frames', missed_app_frames,
               'missed_sf_frames', missed_sf_frames,
               'missed_frames_max_successive', missed_frames_max_successive,
+              'sf_callback_missed_frames', sf_callback_missed_frames,
+              'hwui_callback_missed_frames', hwui_callback_missed_frames,
               'frame_dur_max', frame_dur_max)
             FROM android_jank_cuj_counter_metrics cm
             WHERE cm.cuj_id = cuj.cuj_id),
@@ -111,6 +113,8 @@
               'missed_frames', SUM(app_missed OR sf_missed),
               'missed_app_frames', SUM(app_missed),
               'missed_sf_frames', SUM(sf_missed),
+              'sf_callback_missed_frames', SUM(sf_callback_missed),
+              'hwui_callback_missed_frames', SUM(hwui_callback_missed),
               'frame_dur_max', MAX(f.dur),
               'frame_dur_avg', CAST(AVG(f.dur) AS INTEGER),
               'frame_dur_p50', CAST(PERCENTILE(f.dur, 50) AS INTEGER),
@@ -129,6 +133,8 @@
               'missed_frames', SUM(app_missed OR sf_missed),
               'missed_app_frames', SUM(app_missed),
               'missed_sf_frames', SUM(sf_missed),
+              'sf_callback_missed_frames', SUM(sf_callback_missed),
+              'hwui_callback_missed_frames', SUM(hwui_callback_missed),
               'frame_dur_max', MAX(f.dur),
               'frame_dur_avg', CAST(AVG(f.dur) AS INTEGER),
               'frame_dur_p50', CAST(PERCENTILE(f.dur, 50) AS INTEGER),
@@ -150,7 +156,9 @@
                 'dur', f.dur,
                 'dur_expected', f.dur_expected,
                 'app_missed', f.app_missed,
-                'sf_missed', f.sf_missed))
+                'sf_missed', f.sf_missed,
+                'sf_callback_missed', f.sf_callback_missed,
+                'hwui_callback_missed', f.hwui_callback_missed))
             FROM android_jank_cuj_frame f
             WHERE f.cuj_id = cuj.cuj_id
             ORDER BY frame_number ASC),
diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql
index da39e34..892f6be 100644
--- a/src/trace_processor/metrics/sql/android/android_startup.sql
+++ b/src/trace_processor/metrics/sql/android/android_startup.sql
@@ -209,14 +209,22 @@
       'time_gc_total', (
         SELECT NULL_IF_EMPTY(STARTUP_SLICE_PROTO(TOTAL_GC_TIME_BY_LAUNCH(launches.startup_id)))
       ),
+      'time_dex_open_thread_main',
+      DUR_SUM_MAIN_THREAD_SLICE_PROTO_FOR_LAUNCH(
+        launches.startup_id,
+        'OpenDexFilesFromOat*'),
+      'time_dlopen_thread_main',
+      DUR_SUM_MAIN_THREAD_SLICE_PROTO_FOR_LAUNCH(
+        launches.startup_id,
+        'dlopen:*.so'),
       'time_lock_contention_thread_main',
       DUR_SUM_MAIN_THREAD_SLICE_PROTO_FOR_LAUNCH(
-       launches.startup_id,
+        launches.startup_id,
         'Lock contention on*'
       ),
       'time_monitor_contention_thread_main',
       DUR_SUM_MAIN_THREAD_SLICE_PROTO_FOR_LAUNCH(
-       launches.startup_id,
+        launches.startup_id,
         'Lock contention on a monitor*'
       ),
       'time_before_start_process', (
@@ -286,6 +294,7 @@
       FROM android_thread_slices_for_all_startups
       WHERE startup_id = launches.startup_id AND slice_name GLOB "VerifyClass *"
       ORDER BY slice_dur DESC
+      LIMIT 5
     ),
     'startup_concurrent_to_launch', (
       SELECT RepeatedField(package)
@@ -293,6 +302,11 @@
       WHERE l.startup_id != launches.startup_id
         AND IS_SPANS_OVERLAPPING(l.ts, l.ts_end, launches.ts, launches.ts_end)
     ),
+    'dlopen_file', (
+      SELECT RepeatedField(STR_SPLIT(slice_name, "dlopen: ", 1))
+      FROM android_thread_slices_for_all_startups s
+      WHERE startup_id = launches.startup_id AND slice_name GLOB "dlopen: *.so"
+    ),
     'system_state', AndroidStartupMetric_SystemState(
       'dex2oat_running',
       DUR_OF_PROCESS_RUNNING_CONCURRENT_TO_LAUNCH(launches.startup_id, '*dex2oat64') > 0,
@@ -362,11 +376,10 @@
         WHERE MAIN_THREAD_TIME_FOR_LAUNCH_STATE_AND_IO_WAIT(launches.startup_id, 'D*', TRUE) > 155e6
 
         UNION ALL
-        SELECT 'Time spent in OpenDexFilesFromOat*'
+        SELECT 'Main Thread - Time spent in OpenDexFilesFromOat*'
           AS slow_cause
-        WHERE
-          ANDROID_SUM_DUR_FOR_STARTUP_AND_SLICE(launches.startup_id, 'OpenDexFilesFromOat*')
-            > launches.dur * 0.2
+        WHERE ANDROID_SUM_DUR_ON_MAIN_THREAD_FOR_STARTUP_AND_SLICE(
+          launches.startup_id, 'OpenDexFilesFromOat*') > launches.dur * 0.2
 
         UNION ALL
         SELECT 'Time spent in bindApplication'
@@ -392,9 +405,7 @@
             > launches.dur * 0.15
 
         UNION ALL
-        SELECT 'Potential CPU contention with '
-          || MOST_ACTIVE_PROCESS_FOR_LAUNCH(launches.startup_id)
-          AS slow_cause
+        SELECT 'Potential CPU contention with another process' AS slow_cause
         WHERE MAIN_THREAD_TIME_FOR_LAUNCH_IN_RUNNABLE_STATE(launches.startup_id) > 100e6
           AND MOST_ACTIVE_PROCESS_FOR_LAUNCH(launches.startup_id) IS NOT NULL
 
diff --git a/src/trace_processor/metrics/sql/android/android_surfaceflinger.sql b/src/trace_processor/metrics/sql/android/android_surfaceflinger.sql
index b14ce84..498baf8 100644
--- a/src/trace_processor/metrics/sql/android/android_surfaceflinger.sql
+++ b/src/trace_processor/metrics/sql/android/android_surfaceflinger.sql
@@ -86,6 +86,37 @@
 )
 WHERE event_type = 0 AND fence_id = next_fence_id;
 
+
+DROP VIEW IF EXISTS display_ids;
+CREATE VIEW display_ids AS
+SELECT DISTINCT display_id
+FROM (
+  SELECT display_id FROM frame_missed
+  UNION
+  SELECT display_id FROM hwc_frame_missed
+  UNION
+  SELECT display_id FROM gpu_frame_missed
+);
+
+DROP VIEW IF EXISTS metrics_per_display;
+CREATE VIEW metrics_per_display AS
+SELECT AndroidSurfaceflingerMetric_MetricsPerDisplay(
+  'display_id', d.display_id,
+  'missed_frames',
+  (SELECT COUNT(1) FROM frame_missed WHERE value = 1 AND display_id = d.display_id),
+  'missed_hwc_frames',
+  (SELECT COUNT(1) FROM hwc_frame_missed WHERE value = 1 AND display_id = d.display_id),
+  'missed_gpu_frames',
+  (SELECT COUNT(1) FROM gpu_frame_missed WHERE value = 1 AND display_id = d.display_id),
+  'missed_frame_rate',
+  (SELECT AVG(value) FROM frame_missed WHERE display_id = d.display_id),
+  'missed_hwc_frame_rate',
+  (SELECT AVG(value) FROM hwc_frame_missed WHERE display_id = d.display_id),
+  'missed_gpu_frame_rate',
+  (SELECT AVG(value) FROM gpu_frame_missed WHERE display_id = d.display_id)
+) AS proto
+FROM display_ids d;
+
 DROP VIEW IF EXISTS android_surfaceflinger_output;
 CREATE VIEW android_surfaceflinger_output AS
 SELECT
@@ -99,5 +130,6 @@
     'gpu_invocations', (SELECT COUNT(1) FROM gpu_waiting_end),
     'avg_gpu_waiting_dur_ms', (SELECT AVG(dur) / 1e6 FROM gpu_waiting_span),
     'total_non_empty_gpu_waiting_dur_ms',
-    (SELECT SUM(dur) / 1e6 FROM gpu_waiting_end)
+    (SELECT SUM(dur) / 1e6 FROM gpu_waiting_end),
+    'metrics_per_display', (SELECT RepeatedField(proto) FROM metrics_per_display)
   );
diff --git a/src/trace_processor/metrics/sql/android/frame_missed.sql b/src/trace_processor/metrics/sql/android/frame_missed.sql
index e6546ba..5372177 100644
--- a/src/trace_processor/metrics/sql/android/frame_missed.sql
+++ b/src/trace_processor/metrics/sql/android/frame_missed.sql
@@ -22,12 +22,18 @@
     -- track should ever exist with this name (the track from
     -- surfaceflinger).
     ts - LAG(ts) OVER (ORDER BY ts) AS dur,
+    name,
+    INSTR(name, ' ') AS separator_pos,
     value
   FROM counter c
   JOIN process_counter_track t ON c.track_id = t.id
-  WHERE t.name = '{{track_name}}'
+  WHERE t.name GLOB '{{track_name}}*'
 )
 SELECT
+  CASE
+    WHEN separator_pos = 0 THEN 'unspecified'
+    ELSE SUBSTR(name, separator_pos + 1)
+  END AS display_id,
   ts,
   dur,
   value
diff --git a/src/trace_processor/metrics/sql/android/jank/cujs.sql b/src/trace_processor/metrics/sql/android/jank/cujs.sql
index 2638a8e..03d0d96 100644
--- a/src/trace_processor/metrics/sql/android/jank/cujs.sql
+++ b/src/trace_processor/metrics/sql/android/jank/cujs.sql
@@ -90,11 +90,23 @@
     FROM cuj_state_markers csm
     WHERE csm.cuj_id = cujs.cuj_id AND csm.marker_name GLOB '*layerId#*'
     LIMIT 1
-  ) AS layer_id
+  ) AS layer_id,
+  (
+    SELECT CAST(STR_SPLIT(csm.marker_name, 'beginVsync#', 1) AS INTEGER)
+    FROM cuj_state_markers csm
+    WHERE csm.cuj_id = cujs.cuj_id AND csm.marker_name GLOB '*beginVsync#*'
+    LIMIT 1
+  ) AS begin_vsync,
+  (
+    SELECT CAST(STR_SPLIT(csm.marker_name, 'endVsync#', 1) AS INTEGER)
+    FROM cuj_state_markers csm
+    WHERE csm.cuj_id = cujs.cuj_id AND csm.marker_name GLOB '*endVsync#*'
+    LIMIT 1
+  ) AS end_vsync
 FROM cujs
 WHERE
   state != 'canceled'
   -- Older builds don't have the state markers so we allow NULL but filter out
   -- CUJs that are <4ms long - assuming CUJ was canceled in that case.
   OR (state IS NULL AND cujs.dur > 4e6)
-ORDER BY ts ASC;
+ORDER BY ts ASC;
\ No newline at end of file
diff --git a/src/trace_processor/metrics/sql/android/jank/cujs_boundaries.sql b/src/trace_processor/metrics/sql/android/jank/cujs_boundaries.sql
index 0d91cb8..687b808 100644
--- a/src/trace_processor/metrics/sql/android/jank/cujs_boundaries.sql
+++ b/src/trace_processor/metrics/sql/android/jank/cujs_boundaries.sql
@@ -13,8 +13,9 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
--- Stores the min and max vsync IDs for each of the CUJs.
--- We calculate that by extracting the vsync ID from the
+-- Stores the min and max vsync IDs for each of the CUJs which are extracted
+-- from the CUJ markers. For backward compatibility (In case the markers don't
+-- exist), We calculate that by extracting the vsync ID from the
 -- `Choreographer#doFrame` slices that are within the CUJ markers.
 DROP TABLE IF EXISTS android_jank_cuj_vsync_boundary;
 CREATE TABLE android_jank_cuj_vsync_boundary AS
@@ -22,8 +23,8 @@
   cuj.cuj_id,
   cuj.upid, -- also store upid to simplify further queries
   cuj.layer_id,  -- also store layer_id to simplify further queries
-  MIN(vsync) AS vsync_min,
-  MAX(vsync) AS vsync_max
+  IFNULL(cuj.begin_vsync, MIN(vsync)) AS vsync_min,
+  IFNULL(cuj.end_vsync, MAX(vsync)) AS vsync_max
 FROM android_jank_cuj cuj
 JOIN android_jank_cuj_do_frame_slice USING (cuj_id)
 GROUP BY cuj.cuj_id, cuj.upid, cuj.layer_id;
diff --git a/src/trace_processor/metrics/sql/android/jank/frames.sql b/src/trace_processor/metrics/sql/android/jank/frames.sql
index 66f6639..ffef19d 100644
--- a/src/trace_processor/metrics/sql/android/jank/frames.sql
+++ b/src/trace_processor/metrics/sql/android/jank/frames.sql
@@ -13,6 +13,14 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 
+DROP TABLE IF EXISTS vsync_missed_callback;
+CREATE TABLE vsync_missed_callback AS
+SELECT CAST(STR_SPLIT(name, 'Callback#', 1) AS INTEGER) AS vsync,
+       MAX(name GLOB '*SF*') as sf_callback_missed,
+       MAX(name GLOB '*HWUI*') as hwui_callback_missed
+FROM slice
+WHERE name GLOB '*FT#Missed*Callback*'
+GROUP BY vsync;
 
 DROP TABLE IF EXISTS android_jank_cuj_frame_timeline;
 CREATE TABLE android_jank_cuj_frame_timeline AS
@@ -35,6 +43,8 @@
     OR jank_type GLOB '*SurfaceFlinger Scheduling*'
     OR jank_type GLOB '*Prediction Error*'
     OR jank_type GLOB '*Display HAL*') AS sf_missed,
+  IFNULL(MAX(sf_callback_missed), 0) AS sf_callback_missed,
+  IFNULL(MAX(hwui_callback_missed), 0) AS hwui_callback_missed,
   -- We use MIN to check if ALL layers finished on time
   MIN(on_time_finish) AS on_time_finish,
   MAX(timeline.ts + timeline.dur) AS ts_end_actual,
@@ -54,6 +64,7 @@
     AND vsync <= vsync_max
 LEFT JOIN expected_frame_timeline_slice expected
   ON expected.upid = timeline.upid AND expected.name = timeline.name
+LEFT JOIN vsync_missed_callback missed_callback USING(vsync)
 WHERE
   boundary.layer_id IS NULL
   OR (
@@ -99,6 +110,8 @@
   frame_base.*,
   app_missed,
   sf_missed,
+  sf_callback_missed,
+  hwui_callback_missed,
   on_time_finish,
   ts_end_actual - ts AS dur,
   ts_end_actual - ts_do_frame_start AS dur_unadjusted,
diff --git a/src/trace_processor/metrics/sql/android/jank/internal/counters.sql b/src/trace_processor/metrics/sql/android/jank/internal/counters.sql
index fdcc04c..d04229d 100644
--- a/src/trace_processor/metrics/sql/android/jank/internal/counters.sql
+++ b/src/trace_processor/metrics/sql/android/jank/internal/counters.sql
@@ -51,6 +51,30 @@
   '
 );
 
+DROP TABLE IF EXISTS cuj_marker_missed_callback;
+CREATE TABLE cuj_marker_missed_callback AS
+SELECT
+  marker_track.name AS cuj_slice_name,
+  marker.ts,
+  marker.name AS marker_name
+FROM slice marker
+JOIN track marker_track on  marker_track.id = marker.track_id
+WHERE marker.name GLOB '*FT#Missed*';
+
+SELECT CREATE_FUNCTION(
+   'ANDROID_MISSED_VSYNCS_FOR_CALLBACK(cuj_slice_name STRING, ts_min INT, ts_max INT, callback_missed STRING)',
+   'INT',
+   '
+   SELECT IFNULL(SUM(marker_name GLOB $callback_missed), 0)
+   FROM cuj_marker_missed_callback
+   WHERE
+     cuj_slice_name = $cuj_slice_name
+     AND ts >= $ts_min
+     AND ($ts_max IS NULL OR ts <= $ts_max)
+   ORDER BY ts ASC LIMIT 1
+   '
+);
+
 DROP TABLE IF EXISTS android_jank_cuj_counter_metrics;
 CREATE TABLE android_jank_cuj_counter_metrics AS
 -- Order CUJs to get the ts of the next CUJ with the same name.
@@ -60,6 +84,7 @@
   SELECT
     cuj_id,
     cuj_name,
+    cuj_slice_name,
     upid,
     state,
     ts_end,
@@ -83,5 +108,7 @@
   ANDROID_JANK_CUJ_COUNTER_VALUE(cuj_name, 'missedSfFrames', ts_earliest_allowed_counter, ts_end_next_cuj) AS missed_sf_frames,
   ANDROID_JANK_CUJ_COUNTER_VALUE(cuj_name, 'maxSuccessiveMissedFrames', ts_earliest_allowed_counter, ts_end_next_cuj) AS missed_frames_max_successive,
   -- convert ms to nanos to align with the unit for `dur` in the other tables
-  ANDROID_JANK_CUJ_COUNTER_VALUE(cuj_name, 'maxFrameTimeMillis', ts_earliest_allowed_counter, ts_end_next_cuj) * 1000000 AS frame_dur_max
+  ANDROID_JANK_CUJ_COUNTER_VALUE(cuj_name, 'maxFrameTimeMillis', ts_earliest_allowed_counter, ts_end_next_cuj) * 1000000 AS frame_dur_max,
+  ANDROID_MISSED_VSYNCS_FOR_CALLBACK(cuj_slice_name, ts_earliest_allowed_counter, ts_end_next_cuj, '*SF*') AS sf_callback_missed_frames,
+  ANDROID_MISSED_VSYNCS_FOR_CALLBACK(cuj_slice_name, ts_earliest_allowed_counter, ts_end_next_cuj, '*HWUI*') AS hwui_callback_missed_frames
 FROM cujs_ordered cuj;
diff --git a/src/trace_processor/metrics/sql/android/java_heap_stats.sql b/src/trace_processor/metrics/sql/android/java_heap_stats.sql
index 15627b4..32c43f4 100644
--- a/src/trace_processor/metrics/sql/android/java_heap_stats.sql
+++ b/src/trace_processor/metrics/sql/android/java_heap_stats.sql
@@ -65,7 +65,7 @@
   SELECT * FROM base_stat_counts JOIN heap_roots_proto USING (upid, graph_sample_ts)
 ),
 -- Find closest value
-closest_anon_swap AS (
+closest_anon_swap_oom AS (
   SELECT
     upid,
     graph_sample_ts,
@@ -84,7 +84,23 @@
         -- accept it if close (500ms)
         OR (graph_sample_ts < ts AND diff <= 500 * 1e6)
       ORDER BY diff LIMIT 1
-    ) AS val
+    ) AS anon_swap_val,
+    (
+      SELECT oom_score_val
+      FROM (
+        SELECT
+          ts, dur,
+          oom_score_val,
+          ABS(ts - base_stats.graph_sample_ts) AS diff
+        FROM oom_score_span
+        WHERE upid = base_stats.upid)
+      WHERE
+        (graph_sample_ts >= ts AND graph_sample_ts < ts + dur)
+        -- If the first memory sample for the UPID comes *after* the heap profile
+        -- accept it if close (500ms)
+        OR (graph_sample_ts < ts AND diff <= 500 * 1e6)
+      ORDER BY diff LIMIT 1
+    ) AS oom_score_val
   FROM base_stats
 ),
 -- Group by upid
@@ -100,10 +116,11 @@
       'reachable_heap_native_size', reachable_native_size,
       'reachable_obj_count', reachable_obj_count,
       'roots', roots,
-      'anon_rss_and_swap_size', closest_anon_swap.val
+      'anon_rss_and_swap_size', closest_anon_swap_oom.anon_swap_val,
+      'oom_score_adj', closest_anon_swap_oom.oom_score_val
     )) AS sample_protos
   FROM base_stats
-  LEFT JOIN closest_anon_swap USING (upid, graph_sample_ts)
+  LEFT JOIN closest_anon_swap_oom USING (upid, graph_sample_ts)
   GROUP BY 1
 )
 SELECT JavaHeapStats(
diff --git a/src/trace_processor/metrics/sql/android/network_activity_template.sql b/src/trace_processor/metrics/sql/android/network_activity_template.sql
new file mode 100644
index 0000000..2dcfa31
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/network_activity_template.sql
@@ -0,0 +1,74 @@
+--
+-- 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.
+
+SELECT IMPORT('android.network_packets');
+
+-- Creates a view of aggregated network activity. It is common among networking
+-- to have the interface active for some time after network use. For example, in
+-- mobile networking, it is common to have the cellular interface active for 10
+-- or more seconds after the last packet was sent or received. This view takes
+-- raw packet timing and aggregates it into something that approximates the
+-- activity of the underlying interface.
+--
+-- @arg view_name        The name of the output view.
+-- @arg group_by         Expression to group by (set to 'null' for no grouping).
+-- @arg filter           Expression on `android_network_packets` to filter by.
+-- @arg idle_ns          The amount of time before considering the network idle.
+-- @arg quant_ns         Quantization value, to group rows before the heavy
+--                       part of the query. This should be smaller than idle_ns.
+--
+-- @column group_by      The group_by columns are all present in the output.
+-- @column ts            The timestamp indicating the start of the segment.
+-- @column dur           The duration of the current segment.
+-- @column packet_count  The total number of packets in this segment.
+-- @column packet_length The total number of bytes for packets in this segment.
+CREATE VIEW {{view_name}} AS
+WITH quantized AS (
+  SELECT
+    {{group_by}},
+    MIN(ts) AS ts,
+    MAX(ts+dur)-MIN(ts) AS dur,
+    SUM(packet_count) AS packet_count,
+    SUM(packet_length) AS packet_length
+  FROM android_network_packets
+  WHERE {{filter}}
+  GROUP BY CAST(ts / {{quant_ns}} AS INT64), {{group_by}}
+),
+with_last AS (
+  SELECT
+    *,
+    LAG(ts) OVER (
+      PARTITION BY {{group_by}}
+      ORDER BY ts
+    ) AS last_ts
+  FROM quantized
+),
+with_group AS (
+  SELECT
+    *,
+    COUNT(IIF(ts-last_ts>{{idle_ns}}, 1, null)) OVER (
+      PARTITION BY {{group_by}}
+      ORDER BY ts
+    ) AS group_id
+  FROM with_last
+)
+SELECT
+  {{group_by}},
+  MIN(ts) AS ts,
+  MAX(ts+dur)-MIN(ts)+{{idle_ns}} AS dur,
+  SUM(packet_count) AS packet_count,
+  SUM(packet_length) AS packet_length
+FROM with_group
+GROUP BY group_id, {{group_by}}
diff --git a/src/trace_processor/metrics/sql/android/process_metadata.sql b/src/trace_processor/metrics/sql/android/process_metadata.sql
index acaceb7..5ccf2b1 100644
--- a/src/trace_processor/metrics/sql/android/process_metadata.sql
+++ b/src/trace_processor/metrics/sql/android/process_metadata.sql
@@ -18,7 +18,8 @@
 
 DROP VIEW IF EXISTS process_metadata_table;
 CREATE VIEW process_metadata_table AS
-SELECT * FROM android_process_metadata;
+SELECT android_process_metadata.*, pid FROM android_process_metadata
+JOIN process USING(upid);
 
 DROP VIEW IF EXISTS uid_package_count;
 CREATE VIEW uid_package_count AS
@@ -43,6 +44,7 @@
   NULL_IF_EMPTY(AndroidProcessMetadata(
     'name', process_name,
     'uid', uid,
+    'pid', pid,
     'package', NULL_IF_EMPTY(AndroidProcessMetadata_Package(
       'package_name', package_name,
       'apk_version_code', version_code,
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_tasks_template.sql b/src/trace_processor/metrics/sql/chrome/chrome_tasks_template.sql
index d2886f1..b5464cf 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_tasks_template.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_tasks_template.sql
@@ -427,6 +427,7 @@
   WHERE
     s1.posted_from IN (
       "mojo/public/cpp/system/simple_watcher.cc:Notify",
+      "mojo/public/cpp/system/simple_watcher.cc:ArmOrNotify",
       "mojo/public/cpp/bindings/lib/connector.cc:PostDispatchNextMessageFromPipe",
       "ipc/ipc_mojo_bootstrap.cc:Accept")
 ),
diff --git a/src/trace_processor/prelude/functions/BUILD.gn b/src/trace_processor/prelude/functions/BUILD.gn
index ec4da44..f2f22ba 100644
--- a/src/trace_processor/prelude/functions/BUILD.gn
+++ b/src/trace_processor/prelude/functions/BUILD.gn
@@ -31,12 +31,12 @@
     "layout_functions.h",
     "pprof_functions.cc",
     "pprof_functions.h",
-    "register_function.cc",
-    "register_function.h",
     "sqlite3_str_split.cc",
     "sqlite3_str_split.h",
     "stack_functions.cc",
     "stack_functions.h",
+    "to_ftrace.cc",
+    "to_ftrace.h",
     "utils.h",
     "window_functions.h",
   ]
@@ -57,7 +57,7 @@
     "../../importers/common",
     "../../importers/ftrace:ftrace_descriptors",
     "../../prelude/table_functions",
-    "../../sqlite:sqlite_minimal",
+    "../../sqlite",
     "../../storage",
     "../../types",
     "../../util",
@@ -65,6 +65,20 @@
     "../../util:sql_argument",
     "../../util:stdlib",
   ]
+  public_deps = [ ":interface" ]
+}
+
+source_set("interface") {
+  sources = [
+    "sql_function.cc",
+    "sql_function.h",
+  ]
+  deps = [
+    "../../../../gn:default_deps",
+    "../../../../gn:sqlite",
+    "../../../../include/perfetto/trace_processor:basic_types",
+    "../../../base",
+  ]
 }
 
 perfetto_unittest_source_set("unittests") {
@@ -76,6 +90,6 @@
     "../../../../gn:gtest_and_gmock",
     "../../../../gn:sqlite",
     "../../../base",
-    "../../sqlite:sqlite_minimal",
+    "../../sqlite",
   ]
 }
diff --git a/src/trace_processor/prelude/functions/clock_functions.h b/src/trace_processor/prelude/functions/clock_functions.h
index 34f23f2..aac6280 100644
--- a/src/trace_processor/prelude/functions/clock_functions.h
+++ b/src/trace_processor/prelude/functions/clock_functions.h
@@ -25,7 +25,7 @@
 #include "src/trace_processor/prelude/functions/create_function_internal.h"
 #include "src/trace_processor/util/status_macros.h"
 
-#include "src/trace_processor/prelude/functions/register_function.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/prelude/functions/create_function.cc b/src/trace_processor/prelude/functions/create_function.cc
index ee2e733..c90ad9a 100644
--- a/src/trace_processor/prelude/functions/create_function.cc
+++ b/src/trace_processor/prelude/functions/create_function.cc
@@ -20,6 +20,7 @@
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/prelude/functions/create_function_internal.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/sqlite_engine.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/tp_metatrace.h"
 #include "src/trace_processor/util/status_macros.h"
@@ -31,11 +32,11 @@
 
 struct CreatedFunction : public SqlFunction {
   struct Context {
-    sqlite3* db;
+    SqliteEngine* engine;
     Prototype prototype;
     sql_argument::Type return_type;
     std::string sql;
-    sqlite3_stmt* stmt;
+    ScopedStmt stmt;
   };
 
   static base::Status Run(Context* ctx,
@@ -88,20 +89,21 @@
 
   // Bind all the arguments to the appropriate places in the function.
   for (size_t i = 0; i < argc; ++i) {
-    RETURN_IF_ERROR(MaybeBindArgument(ctx->stmt, ctx->prototype.function_name,
+    RETURN_IF_ERROR(MaybeBindArgument(ctx->stmt.get(),
+                                      ctx->prototype.function_name,
                                       ctx->prototype.arguments[i], argv[i]));
   }
 
-  int ret = sqlite3_step(ctx->stmt);
+  int ret = sqlite3_step(ctx->stmt.get());
   RETURN_IF_ERROR(
-      SqliteRetToStatus(ctx->db, ctx->prototype.function_name, ret));
+      SqliteRetToStatus(ctx->engine->db(), ctx->prototype.function_name, ret));
   if (ret == SQLITE_DONE) {
     // No return value means we just return don't set |out|.
     return base::OkStatus();
   }
 
   PERFETTO_DCHECK(ret == SQLITE_ROW);
-  size_t col_count = static_cast<size_t>(sqlite3_column_count(ctx->stmt));
+  size_t col_count = static_cast<size_t>(sqlite3_column_count(ctx->stmt.get()));
   if (col_count != 1) {
     return base::ErrStatus(
         "%s: SQL definition should only return one column: returned %zu "
@@ -109,7 +111,8 @@
         ctx->prototype.function_name.c_str(), col_count);
   }
 
-  out = sqlite_utils::SqliteValueToSqlValue(sqlite3_column_value(ctx->stmt, 0));
+  out = sqlite_utils::SqliteValueToSqlValue(
+      sqlite3_column_value(ctx->stmt.get(), 0));
 
   // If we return a bytes type but have a null pointer, SQLite will convert this
   // to an SQL null. However, for proto build functions, we actively want to
@@ -123,11 +126,11 @@
 }
 
 base::Status CreatedFunction::VerifyPostConditions(Context* ctx) {
-  int ret = sqlite3_step(ctx->stmt);
+  int ret = sqlite3_step(ctx->stmt.get());
   RETURN_IF_ERROR(
-      SqliteRetToStatus(ctx->db, ctx->prototype.function_name, ret));
+      SqliteRetToStatus(ctx->engine->db(), ctx->prototype.function_name, ret));
   if (ret == SQLITE_ROW) {
-    auto expanded_sql = sqlite_utils::ExpandedSqlForStmt(ctx->stmt);
+    auto expanded_sql = sqlite_utils::ExpandedSqlForStmt(ctx->stmt.get());
     return base::ErrStatus(
         "%s: multiple values were returned when executing function body. "
         "Executed SQL was %s",
@@ -138,21 +141,13 @@
 }
 
 void CreatedFunction::Cleanup(CreatedFunction::Context* ctx) {
-  sqlite3_reset(ctx->stmt);
-  sqlite3_clear_bindings(ctx->stmt);
+  sqlite3_reset(ctx->stmt.get());
+  sqlite3_clear_bindings(ctx->stmt.get());
 }
 
 }  // namespace
 
-size_t CreateFunction::NameAndArgc::Hasher::operator()(
-    const NameAndArgc& s) const noexcept {
-  base::Hasher hash;
-  hash.Update(s.name.data(), s.name.size());
-  hash.Update(s.argc);
-  return static_cast<size_t>(hash.digest());
-}
-
-base::Status CreateFunction::Run(CreateFunction::Context* ctx,
+base::Status CreateFunction::Run(SqliteEngine* engine,
                                  size_t argc,
                                  sqlite3_value** argv,
                                  SqlValue&,
@@ -216,16 +211,14 @@
   }
 
   int created_argc = static_cast<int>(prototype.arguments.size());
-  NameAndArgc key{prototype.function_name, created_argc};
-  auto it = ctx->state->find(key);
-  if (it != ctx->state->end()) {
+  auto* fn_ctx =
+      engine->GetFunctionContext(prototype.function_name, created_argc);
+  if (fn_ctx) {
     // If the function already exists, just verify that the prototype, return
     // type and SQL matches exactly with what we already had registered. By
     // doing this, we can avoid the problem plaguing C++ macros where macro
     // ordering determines which one gets run.
-    auto* created_ctx = static_cast<CreatedFunction::Context*>(
-        it->second.created_functon_context);
-
+    auto* created_ctx = static_cast<CreatedFunction::Context*>(fn_ctx);
     if (created_ctx->prototype != prototype) {
       return base::ErrStatus(
           "CREATE_FUNCTION[prototype=%s]: function prototype changed",
@@ -252,7 +245,7 @@
   // Prepare the SQL definition as a statement using SQLite.
   ScopedStmt stmt;
   sqlite3_stmt* stmt_raw = nullptr;
-  int ret = sqlite3_prepare_v2(ctx->db, sql_defn_str.data(),
+  int ret = sqlite3_prepare_v2(engine->db(), sql_defn_str.data(),
                                static_cast<int>(sql_defn_str.size()), &stmt_raw,
                                nullptr);
   if (ret != SQLITE_OK) {
@@ -261,19 +254,18 @@
         "statement %s",
         prototype_str.ToStdString().c_str(),
         sqlite_utils::FormatErrorMessage(
-            stmt_raw, base::StringView(sql_defn_str), ctx->db, ret)
+            stmt_raw, base::StringView(sql_defn_str), engine->db(), ret)
             .c_message());
   }
   stmt.reset(stmt_raw);
 
-  std::unique_ptr<CreatedFunction::Context> created(
-      new CreatedFunction::Context{ctx->db, std::move(prototype),
+  std::string function_name = prototype.function_name;
+  std::unique_ptr<CreatedFunction::Context> created_fn_ctx(
+      new CreatedFunction::Context{engine, std::move(prototype),
                                    *opt_return_type, std::move(sql_defn_str),
-                                   stmt.get()});
-  CreatedFunction::Context* created_ptr = created.get();
-  RETURN_IF_ERROR(RegisterSqlFunction<CreatedFunction>(
-      ctx->db, key.name.c_str(), created_argc, std::move(created)));
-  ctx->state->emplace(key, PerFunctionState{std::move(stmt), created_ptr});
+                                   std::move(stmt)});
+  RETURN_IF_ERROR(engine->RegisterSqlFunction<CreatedFunction>(
+      function_name.c_str(), created_argc, std::move(created_fn_ctx)));
 
   // CREATE_FUNCTION doesn't have a return value so just don't sent |out|.
   return base::OkStatus();
diff --git a/src/trace_processor/prelude/functions/create_function.h b/src/trace_processor/prelude/functions/create_function.h
index 258c4bb..0dd6f4c 100644
--- a/src/trace_processor/prelude/functions/create_function.h
+++ b/src/trace_processor/prelude/functions/create_function.h
@@ -20,7 +20,9 @@
 #include <sqlite3.h>
 #include <unordered_map>
 
-#include "src/trace_processor/prelude/functions/register_function.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
+#include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/sqlite_table.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -34,25 +36,7 @@
     // void* to avoid leaking state.
     void* created_functon_context;
   };
-  struct NameAndArgc {
-    std::string name;
-    int argc;
-
-    struct Hasher {
-      std::size_t operator()(const NameAndArgc& s) const noexcept;
-    };
-    bool operator==(const NameAndArgc& other) const {
-      return name == other.name && argc == other.argc;
-    }
-  };
-  using State = std::unordered_map<NameAndArgc,
-                                   CreateFunction::PerFunctionState,
-                                   NameAndArgc::Hasher>;
-
-  struct Context {
-    sqlite3* db;
-    State* state;
-  };
+  using Context = SqliteEngine;
 
   static constexpr bool kVoidReturn = true;
 
diff --git a/src/trace_processor/prelude/functions/create_view_function.cc b/src/trace_processor/prelude/functions/create_view_function.cc
index 3989de4..952924f 100644
--- a/src/trace_processor/prelude/functions/create_view_function.cc
+++ b/src/trace_processor/prelude/functions/create_view_function.cc
@@ -24,6 +24,7 @@
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/prelude/functions/create_function_internal.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/sqlite_engine.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/tp_metatrace.h"
@@ -34,19 +35,20 @@
 
 namespace {
 
-class CreatedViewFunction : public SqliteTable {
+class CreatedViewFunction final
+    : public TypedSqliteTable<CreatedViewFunction, void*> {
  public:
-  class Cursor : public SqliteTable::Cursor {
+  class Cursor final : public SqliteTable::BaseCursor {
    public:
     explicit Cursor(CreatedViewFunction* table);
-    ~Cursor() override;
+    ~Cursor() final;
 
-    int Filter(const QueryConstraints& qc,
-               sqlite3_value**,
-               FilterHistory) override;
-    int Next() override;
-    int Eof() override;
-    int Column(sqlite3_context* context, int N) override;
+    base::Status Filter(const QueryConstraints& qc,
+                        sqlite3_value**,
+                        FilterHistory);
+    base::Status Next();
+    bool Eof();
+    base::Status Column(sqlite3_context* context, int N);
 
    private:
     ScopedStmt scoped_stmt_;
@@ -57,16 +59,11 @@
   };
 
   CreatedViewFunction(sqlite3*, void*);
-  ~CreatedViewFunction() override;
+  ~CreatedViewFunction() final;
 
-  base::Status Init(int argc, const char* const* argv, Schema*) override;
-  std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
-  int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) override;
-
-  static void Register(sqlite3* db) {
-    SqliteTable::Register<CreatedViewFunction>(
-        db, nullptr, "internal_view_function_impl", false, true);
-  }
+  base::Status Init(int argc, const char* const* argv, Schema*) final;
+  std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
+  int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) final;
 
  private:
   Schema CreateSchema();
@@ -246,7 +243,7 @@
   return SqliteTable::Schema(std::move(columns), std::move(primary_keys));
 }
 
-std::unique_ptr<SqliteTable::Cursor> CreatedViewFunction::CreateCursor() {
+std::unique_ptr<SqliteTable::BaseCursor> CreatedViewFunction::CreateCursor() {
   return std::unique_ptr<Cursor>(new Cursor(this));
 }
 
@@ -272,13 +269,13 @@
 }
 
 CreatedViewFunction::Cursor::Cursor(CreatedViewFunction* table)
-    : SqliteTable::Cursor(table), table_(table) {}
+    : SqliteTable::BaseCursor(table), table_(table) {}
 
 CreatedViewFunction::Cursor::~Cursor() = default;
 
-int CreatedViewFunction::Cursor::Filter(const QueryConstraints& qc,
-                                        sqlite3_value** argv,
-                                        FilterHistory) {
+base::Status CreatedViewFunction::Cursor::Filter(const QueryConstraints& qc,
+                                                 sqlite3_value** argv,
+                                                 FilterHistory) {
   PERFETTO_TP_TRACE(metatrace::Category::FUNCTION, "CREATE_VIEW_FUNCTION",
                     [this](metatrace::Record* r) {
                       r->AddArg("Function",
@@ -302,10 +299,8 @@
     // We only support equality constraints as we're expecting "input arguments"
     // to our "function".
     if (!sqlite_utils::IsOpEq(cs.op)) {
-      table_->SetErrorMessage(
-          sqlite3_mprintf("%s: non-equality constraint passed",
-                          table_->prototype_.function_name.c_str()));
-      return SQLITE_ERROR;
+      return base::ErrStatus("%s: non-equality constraint passed",
+                             table_->prototype_.function_name.c_str());
     }
 
     const auto& arg = table_->prototype_.arguments[col_to_arg_idx(cs.column)];
@@ -313,11 +308,9 @@
         argv[i], sql_argument::TypeToSqlValueType(arg.type()),
         sql_argument::TypeToHumanFriendlyString(arg.type()));
     if (!status.ok()) {
-      table_->SetErrorMessage(
-          sqlite3_mprintf("%s: argument %s (index %u) %s",
-                          table_->prototype_.function_name.c_str(),
-                          arg.name().c_str(), i, status.c_message()));
-      return SQLITE_ERROR;
+      return base::ErrStatus("%s: argument %s (index %zu) %s",
+                             table_->prototype_.function_name.c_str(),
+                             arg.name().c_str(), i, status.c_message());
     }
 
     seen_argument_constraints++;
@@ -325,12 +318,11 @@
 
   // Verify that we saw one valid constriant for every input argument.
   if (seen_argument_constraints < table_->prototype_.arguments.size()) {
-    table_->SetErrorMessage(sqlite3_mprintf(
-        "%s: missing value for input argument. Saw %u arguments but expected "
-        "%u",
+    return base::ErrStatus(
+        "%s: missing value for input argument. Saw %zu arguments but expected "
+        "%zu",
         table_->prototype_.function_name.c_str(), seen_argument_constraints,
-        table_->prototype_.arguments.size()));
-    return SQLITE_ERROR;
+        table_->prototype_.arguments.size());
   }
 
   // Prepare the SQL definition as a statement using SQLite.
@@ -361,10 +353,7 @@
     const auto& arg = table_->prototype_.arguments[index];
     auto status = MaybeBindArgument(stmt_, table_->prototype_.function_name,
                                     arg, argv[i]);
-    if (!status.ok()) {
-      table_->SetErrorMessage(sqlite3_mprintf("%s", status.c_message()));
-      return SQLITE_ERROR;
-    }
+    RETURN_IF_ERROR(status);
   }
 
   // Reset the next call count - this is necessary because the same cursor
@@ -373,26 +362,25 @@
   return Next();
 }
 
-int CreatedViewFunction::Cursor::Next() {
+base::Status CreatedViewFunction::Cursor::Next() {
   int ret = sqlite3_step(stmt_);
   is_eof_ = ret == SQLITE_DONE;
   next_call_count_++;
   if (ret != SQLITE_ROW && ret != SQLITE_DONE) {
-    table_->SetErrorMessage(sqlite3_mprintf(
+    return base::ErrStatus(
         "%s: SQLite error while stepping statement: %s",
         table_->prototype_.function_name.c_str(),
         sqlite_utils::FormatErrorMessage(stmt_, std::nullopt, table_->db_, ret)
-            .c_message()));
-    return ret;
+            .c_message());
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int CreatedViewFunction::Cursor::Eof() {
+bool CreatedViewFunction::Cursor::Eof() {
   return is_eof_;
 }
 
-int CreatedViewFunction::Cursor::Column(sqlite3_context* ctx, int i) {
+base::Status CreatedViewFunction::Cursor::Column(sqlite3_context* ctx, int i) {
   size_t idx = static_cast<size_t>(i);
   if (table_->IsReturnValueColumn(idx)) {
     sqlite3_result_value(ctx, sqlite3_column_value(stmt_, i));
@@ -406,7 +394,7 @@
     PERFETTO_DCHECK(table_->IsPrimaryKeyColumn(idx));
     sqlite3_result_int(ctx, next_call_count_);
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
 }  // namespace
@@ -494,8 +482,10 @@
   return base::OkStatus();
 }
 
-void CreateViewFunction::RegisterTable(sqlite3* db) {
-  CreatedViewFunction::Register(db);
+void RegisterCreateViewFunctionModule(SqliteEngine* engine) {
+  engine->RegisterVirtualTableModule<CreatedViewFunction>(
+      "internal_view_function_impl", nullptr,
+      SqliteTable::TableType::kExplicitCreate, false);
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/prelude/functions/create_view_function.h b/src/trace_processor/prelude/functions/create_view_function.h
index c91817a..509ea5e 100644
--- a/src/trace_processor/prelude/functions/create_view_function.h
+++ b/src/trace_processor/prelude/functions/create_view_function.h
@@ -20,11 +20,13 @@
 #include <sqlite3.h>
 #include <unordered_map>
 
-#include "src/trace_processor/prelude/functions/register_function.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
 
 namespace perfetto {
 namespace trace_processor {
 
+class SqliteEngine;
+
 // Implementation of CREATE_VIEW_FUNCTION SQL function.
 // See https://perfetto.dev/docs/analysis/metrics#metric-helper-functions for
 // usage of this function.
@@ -40,10 +42,10 @@
                           sqlite3_value** argv,
                           SqlValue& out,
                           Destructors&);
-
-  static void RegisterTable(sqlite3* db);
 };
 
+void RegisterCreateViewFunctionModule(SqliteEngine*);
+
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/prelude/functions/import.h b/src/trace_processor/prelude/functions/import.h
index 9773b5f..da40f5f 100644
--- a/src/trace_processor/prelude/functions/import.h
+++ b/src/trace_processor/prelude/functions/import.h
@@ -23,7 +23,7 @@
 
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/trace_processor/trace_processor.h"
-#include "src/trace_processor/prelude/functions/register_function.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
 #include "src/trace_processor/util/sql_modules.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/prelude/functions/register_function.h b/src/trace_processor/prelude/functions/register_function.h
deleted file mode 100644
index acca3e6..0000000
--- a/src/trace_processor/prelude/functions/register_function.h
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_REGISTER_FUNCTION_H_
-#define SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_REGISTER_FUNCTION_H_
-
-#include <sqlite3.h>
-#include <memory>
-
-#include "src/trace_processor/sqlite/sqlite_utils.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-// Prototype for a C++ function which can be registered with SQLite.
-//
-// Usage
-//
-// Define a subclass of this struct as follows:
-// struct YourFunction : public SqlFunction {
-//   // Optional if you want a custom context object (i.e. an object
-//   // passed in at registration time which will be passed to Run on
-//   // every invocation)
-//   struct YourContext { /* define context fields here */ };
-//
-//   static base::Status Run(/* see parameters below */) {
-//     /* function body here */
-//   }
-//
-//   static base::Status Cleanup(/* see parameters below */) {
-//     /* function body here */
-//   }
-// }
-//
-// Then, register this function with SQLite using RegisterFunction (see below);
-// you'll likely want to do this in TraceProcessorImpl:
-// RegisterFunction<YourFunction>(/* see arguments below */)
-struct SqlFunction {
-  // The type of the context object which will be passed to the function.
-  // Can be redefined in any sub-classes to override the context.
-  using Context = void;
-
-  // Indicates whether this function is "void" (i.e. doesn't actually want
-  // to return a value). While the function will still return null in SQL
-  // (because SQLite does not actually allow null functions), for accounting
-  // purposes, this null will be ignored when verifying whether this statement
-  // has any output.
-  // Can be redefined in any sub-classes to override it.
-  // If this is set to true, subclasses must not modify |out| or |destructors|.
-  static constexpr bool kVoidReturn = false;
-
-  // Struct which holds destructors for strings/bytes returned from the
-  // function. Passed as an argument to |Run| to allow implementations to
-  // override the destructors.
-  struct Destructors {
-    sqlite3_destructor_type string_destructor = sqlite_utils::kSqliteTransient;
-    sqlite3_destructor_type bytes_destructor = sqlite_utils::kSqliteTransient;
-  };
-
-  // The function which will be exectued with the arguments from SQL.
-  //
-  // Implementations MUST define this function themselves; this function is
-  // declared but *not* defined so linker errors will be thrown if not defined.
-  //
-  // |ctx|:         the context object passed at registration time.
-  // |argc|:        number of arguments.
-  // |argv|:        arguments to the function.
-  // |out|:         the return value of the function.
-  // |destructors|: destructors for string/bytes return values.
-  static base::Status Run(Context* ctx,
-                          size_t argc,
-                          sqlite3_value** argv,
-                          SqlValue& out,
-                          Destructors& destructors);
-
-  // Executed after the result from |Run| is reported to SQLite.
-  // Allows implementations to verify post-conditions without needing to worry
-  // about overwriting return types.
-  //
-  // Implementations do not need to define this function; a default no-op
-  // implementation will be used in this case.
-  static base::Status VerifyPostConditions(Context*);
-
-  // Executed after the result from |Run| is reported to SQLite.
-  // Allows any pending state to be cleaned up post-copy of results by SQLite:
-  // this function will be called even if |Run| or |PostRun| returned errors.
-  //
-  // Implementations do not need to define this function; a default no-op
-  // implementation will be used in this case.
-  static void Cleanup(Context*);
-};
-
-// Registers a C++ function to be runnable from SQL.
-// The format of the function is given by the |SqlFunction|; see the
-// documentaion above.
-//
-// |db|:          sqlite3 database object
-// |name|:        name of the function in SQL
-// |argc|:        number of arguments for this function, -1 if variable
-// |ctx|:         context object for the function (see SqlFunction::Run above);
-//                this object *must* outlive the function so should likely be
-//                either static or scoped to the lifetime of TraceProcessor.
-// |determistic|: whether this function has deterministic output given the
-//                same set of arguments.
-template <typename Function>
-base::Status RegisterSqlFunction(sqlite3* db,
-                                 const char* name,
-                                 int argc,
-                                 typename Function::Context* ctx,
-                                 bool deterministic = true);
-
-// Same as above except allows a unique_ptr to be passed for the context; this
-// allows for SQLite to manage the lifetime of this pointer instead of the
-// essentially static requirement of the context pointer above.
-template <typename Function>
-base::Status RegisterSqlFunction(
-    sqlite3* db,
-    const char* name,
-    int argc,
-    std::unique_ptr<typename Function::Context> ctx,
-    bool deterministic = true);
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-// The rest of this file is just implementation details which we need
-// in the header file because it is templated code. We separate it out
-// like this to keep the API people actually care about easy to read.
-
-namespace perfetto {
-namespace trace_processor {
-
-namespace sqlite_internal {
-
-// RAII type to call Function::Cleanup when destroyed.
-template <typename Function>
-struct ScopedCleanup {
-  typename Function::Context* ctx;
-  ~ScopedCleanup() { Function::Cleanup(ctx); }
-};
-
-template <typename Function>
-void WrapSqlFunction(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
-  using Context = typename Function::Context;
-  Context* ud = static_cast<Context*>(sqlite3_user_data(ctx));
-
-  ScopedCleanup<Function> scoped_cleanup{ud};
-  SqlValue value{};
-  SqlFunction::Destructors destructors{};
-  base::Status status =
-      Function::Run(ud, static_cast<size_t>(argc), argv, value, destructors);
-  if (!status.ok()) {
-    sqlite3_result_error(ctx, status.c_message(), -1);
-    return;
-  }
-
-  if (Function::kVoidReturn) {
-    if (!value.is_null()) {
-      sqlite3_result_error(ctx, "void SQL function returned value", -1);
-      return;
-    }
-
-    // If the function doesn't want to return anything, set the "VOID"
-    // pointer type to a non-null value. Note that because of the weird
-    // way |sqlite3_value_pointer| works, we need to set some value even
-    // if we don't actually read it - just set it to a pointer to an empty
-    // string for this reason.
-    static char kVoidValue[] = "";
-    sqlite3_result_pointer(ctx, kVoidValue, "VOID", nullptr);
-  } else {
-    sqlite_utils::ReportSqlValue(ctx, value, destructors.string_destructor,
-                                 destructors.bytes_destructor);
-  }
-
-  status = Function::VerifyPostConditions(ud);
-  if (!status.ok()) {
-    sqlite3_result_error(ctx, status.c_message(), -1);
-    return;
-  }
-}
-}  // namespace sqlite_internal
-
-template <typename Function>
-base::Status RegisterSqlFunction(sqlite3* db,
-                                 const char* name,
-                                 int argc,
-                                 typename Function::Context* ctx,
-                                 bool deterministic) {
-  int flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0);
-  int ret = sqlite3_create_function_v2(
-      db, name, static_cast<int>(argc), flags, ctx,
-      sqlite_internal::WrapSqlFunction<Function>, nullptr, nullptr, nullptr);
-  if (ret != SQLITE_OK) {
-    return base::ErrStatus("Unable to register function with name %s", name);
-  }
-  return base::OkStatus();
-}
-
-template <typename Function>
-base::Status RegisterSqlFunction(
-    sqlite3* db,
-    const char* name,
-    int argc,
-    std::unique_ptr<typename Function::Context> user_data,
-    bool deterministic) {
-  int flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0);
-  int ret = sqlite3_create_function_v2(
-      db, name, static_cast<int>(argc), flags, user_data.release(),
-      sqlite_internal::WrapSqlFunction<Function>, nullptr, nullptr,
-      [](void* ptr) { delete static_cast<typename Function::Context*>(ptr); });
-  if (ret != SQLITE_OK) {
-    return base::ErrStatus("Unable to register function with name %s", name);
-  }
-  return base::OkStatus();
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_REGISTER_FUNCTION_H_
diff --git a/src/trace_processor/prelude/functions/register_function.cc b/src/trace_processor/prelude/functions/sql_function.cc
similarity index 80%
rename from src/trace_processor/prelude/functions/register_function.cc
rename to src/trace_processor/prelude/functions/sql_function.cc
index ef41f1d..26bac5e 100644
--- a/src/trace_processor/prelude/functions/register_function.cc
+++ b/src/trace_processor/prelude/functions/sql_function.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/prelude/functions/register_function.h"
-#include "sqlite3.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/prelude/functions/sql_function.h b/src/trace_processor/prelude/functions/sql_function.h
new file mode 100644
index 0000000..ba7fab7
--- /dev/null
+++ b/src/trace_processor/prelude/functions/sql_function.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_SQL_FUNCTION_H_
+#define SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_SQL_FUNCTION_H_
+
+#include <sqlite3.h>
+#include <memory>
+
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Prototype for a C++ function which can be registered with SQLite.
+//
+// Usage
+//
+// Define a subclass of this struct as follows:
+// struct YourFunction : public SqlFunction {
+//   // Optional if you want a custom context object (i.e. an object
+//   // passed in at registration time which will be passed to Run on
+//   // every invocation)
+//   struct YourContext { /* define context fields here */ };
+//
+//   static base::Status Run(/* see parameters below */) {
+//     /* function body here */
+//   }
+//
+//   static base::Status Cleanup(/* see parameters below */) {
+//     /* function body here */
+//   }
+// }
+//
+// Then, register this function with SQLite using RegisterFunction (see below);
+// you'll likely want to do this in TraceProcessorImpl:
+// RegisterFunction<YourFunction>(/* see arguments below */)
+struct SqlFunction {
+  // The type of the context object which will be passed to the function.
+  // Can be redefined in any sub-classes to override the context.
+  using Context = void;
+
+  // Indicates whether this function is "void" (i.e. doesn't actually want
+  // to return a value). While the function will still return null in SQL
+  // (because SQLite does not actually allow null functions), for accounting
+  // purposes, this null will be ignored when verifying whether this statement
+  // has any output.
+  // Can be redefined in any sub-classes to override it.
+  // If this is set to true, subclasses must not modify |out| or |destructors|.
+  static constexpr bool kVoidReturn = false;
+
+  // Struct which holds destructors for strings/bytes returned from the
+  // function. Passed as an argument to |Run| to allow implementations to
+  // override the destructors.
+  struct Destructors {
+    // This matches SQLITE_TRANSIENT constant which we cannot use because it
+    // expands to a C-style cast, causing compiler warnings.
+    sqlite3_destructor_type string_destructor =
+        reinterpret_cast<sqlite3_destructor_type>(-1);
+    sqlite3_destructor_type bytes_destructor =
+        reinterpret_cast<sqlite3_destructor_type>(-1);
+  };
+
+  // The function which will be executed with the arguments from SQL.
+  //
+  // Implementations MUST define this function themselves; this function is
+  // declared but *not* defined so linker errors will be thrown if not defined.
+  //
+  // |ctx|:         the context object passed at registration time.
+  // |argc|:        number of arguments.
+  // |argv|:        arguments to the function.
+  // |out|:         the return value of the function.
+  // |destructors|: destructors for string/bytes return values.
+  static base::Status Run(Context* ctx,
+                          size_t argc,
+                          sqlite3_value** argv,
+                          SqlValue& out,
+                          Destructors& destructors);
+
+  // Executed after the result from |Run| is reported to SQLite.
+  // Allows implementations to verify post-conditions without needing to worry
+  // about overwriting return types.
+  //
+  // Implementations do not need to define this function; a default no-op
+  // implementation will be used in this case.
+  static base::Status VerifyPostConditions(Context*);
+
+  // Executed after the result from |Run| is reported to SQLite.
+  // Allows any pending state to be cleaned up post-copy of results by SQLite:
+  // this function will be called even if |Run| or |PostRun| returned errors.
+  //
+  // Implementations do not need to define this function; a default no-op
+  // implementation will be used in this case.
+  static void Cleanup(Context*);
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_SQL_FUNCTION_H_
diff --git a/src/trace_processor/prelude/functions/stack_functions.cc b/src/trace_processor/prelude/functions/stack_functions.cc
index 2f8730b..e2c4468 100644
--- a/src/trace_processor/prelude/functions/stack_functions.cc
+++ b/src/trace_processor/prelude/functions/stack_functions.cc
@@ -31,7 +31,8 @@
 #include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/status.h"
 #include "protos/perfetto/trace_processor/stack.pbzero.h"
-#include "src/trace_processor/prelude/functions/register_function.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
+#include "src/trace_processor/sqlite/sqlite_engine.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
@@ -243,15 +244,16 @@
 
 }  // namespace
 
-base::Status RegisterStackFunctions(sqlite3* db,
+base::Status RegisterStackFunctions(SqliteEngine* engine,
                                     TraceProcessorContext* context) {
-  RETURN_IF_ERROR(RegisterSqlFunction<CatStacksFunction>(
-      db, CatStacksFunction::kFunctionName, -1, context->storage.get()));
-  RETURN_IF_ERROR(RegisterSqlFunction<StackFromStackProfileFrameFunction>(
-      db, StackFromStackProfileFrameFunction::kFunctionName, 1,
-      context->storage.get()));
-  return RegisterSqlFunction<StackFromStackProfileCallsiteFunction>(
-      db, StackFromStackProfileCallsiteFunction::kFunctionName, -1,
+  RETURN_IF_ERROR(engine->RegisterSqlFunction<CatStacksFunction>(
+      CatStacksFunction::kFunctionName, -1, context->storage.get()));
+  RETURN_IF_ERROR(
+      engine->RegisterSqlFunction<StackFromStackProfileFrameFunction>(
+          StackFromStackProfileFrameFunction::kFunctionName, 1,
+          context->storage.get()));
+  return engine->RegisterSqlFunction<StackFromStackProfileCallsiteFunction>(
+      StackFromStackProfileCallsiteFunction::kFunctionName, -1,
       context->storage.get());
 }
 
diff --git a/src/trace_processor/prelude/functions/stack_functions.h b/src/trace_processor/prelude/functions/stack_functions.h
index 5acb046..7fd676c 100644
--- a/src/trace_processor/prelude/functions/stack_functions.h
+++ b/src/trace_processor/prelude/functions/stack_functions.h
@@ -26,6 +26,7 @@
 namespace perfetto {
 namespace trace_processor {
 
+class SqliteEngine;
 class TraceProcessorContext;
 
 // Registers the stack manipulation related functions:
@@ -49,7 +50,7 @@
 // it generates a fake Frame
 //
 // See protos/perfetto/trace_processor/stack.proto
-base::Status RegisterStackFunctions(sqlite3* db,
+base::Status RegisterStackFunctions(SqliteEngine* engine,
                                     TraceProcessorContext* context);
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/sqlite/sqlite_raw_table.cc b/src/trace_processor/prelude/functions/to_ftrace.cc
similarity index 93%
rename from src/trace_processor/sqlite/sqlite_raw_table.cc
rename to src/trace_processor/prelude/functions/to_ftrace.cc
index feaca00..5bb138f 100644
--- a/src/trace_processor/sqlite/sqlite_raw_table.cc
+++ b/src/trace_processor/prelude/functions/to_ftrace.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/sqlite/sqlite_raw_table.h"
-
-#include <cinttypes>
+#include "src/trace_processor/prelude/functions/to_ftrace.h"
 
 #include "perfetto/base/compiler.h"
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/importers/common/system_info_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
@@ -538,46 +538,24 @@
 
 }  // namespace
 
-SqliteRawTable::SqliteRawTable(sqlite3* db, Context context)
-    : DbSqliteTable(db,
-                    {context.cache, TableComputation::kStatic,
-                     &context.context->storage->raw_table(), nullptr}),
-      serializer_(context.context) {
-  auto fn = [](sqlite3_context* ctx, int argc, sqlite3_value** argv) {
-    auto* thiz = static_cast<SqliteRawTable*>(sqlite3_user_data(ctx));
-    thiz->ToSystrace(ctx, argc, argv);
-  };
-  sqlite3_create_function(db, "to_ftrace", 1,
-                          SQLITE_UTF8 | SQLITE_DETERMINISTIC, this, fn, nullptr,
-                          nullptr);
-}
-
-SqliteRawTable::~SqliteRawTable() = default;
-
-void SqliteRawTable::RegisterTable(sqlite3* db,
-                                   QueryCache* cache,
-                                   TraceProcessorContext* context) {
-  SqliteTable::Register<SqliteRawTable, Context>(db, Context{cache, context},
-                                                 "raw");
-}
-
-void SqliteRawTable::ToSystrace(sqlite3_context* ctx,
-                                int argc,
-                                sqlite3_value** argv) {
+base::Status ToFtrace::Run(Context* context,
+                           size_t argc,
+                           sqlite3_value** argv,
+                           SqlValue& out,
+                           Destructors& destructors) {
   if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_INTEGER) {
-    sqlite3_result_error(ctx, "Usage: to_ftrace(id)", -1);
-    return;
+    return base::ErrStatus("Usage: to_ftrace(id)");
   }
   uint32_t row = static_cast<uint32_t>(sqlite3_value_int64(argv[0]));
 
-  auto str = serializer_.SerializeToString(row);
+  auto str = context->serializer.SerializeToString(row);
   if (str.get() == nullptr) {
-    base::StackString<128> err("to_ftrace: Cannot serialize row id %u", row);
-    sqlite3_result_error(ctx, err.c_str(), -1);
-    return;
+    return base::ErrStatus("to_ftrace: Cannot serialize row id %u", row);
   }
 
-  sqlite3_result_text(ctx, str.release(), -1, str.get_deleter());
+  out = SqlValue::String(str.release());
+  destructors.string_destructor = str.get_deleter();
+  return base::OkStatus();
 }
 
 SystraceSerializer::SystraceSerializer(TraceProcessorContext* context)
diff --git a/src/trace_processor/sqlite/sqlite_raw_table.h b/src/trace_processor/prelude/functions/to_ftrace.h
similarity index 65%
rename from src/trace_processor/sqlite/sqlite_raw_table.h
rename to src/trace_processor/prelude/functions/to_ftrace.h
index dcb5c46..9c55067 100644
--- a/src/trace_processor/sqlite/sqlite_raw_table.h
+++ b/src/trace_processor/prelude/functions/to_ftrace.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,16 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_RAW_TABLE_H_
-#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_RAW_TABLE_H_
+#ifndef SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_TO_FTRACE_H_
+#define SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_TO_FTRACE_H_
 
-#include "perfetto/base/logging.h"
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/string_writer.h"
-#include "src/trace_processor/sqlite/db_sqlite_table.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
-#include "src/trace_processor/types/variadic.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -47,25 +45,20 @@
   TraceProcessorContext* context_ = nullptr;
 };
 
-class SqliteRawTable : public DbSqliteTable {
- public:
+struct ToFtrace : public SqlFunction {
   struct Context {
-    QueryCache* cache;
-    TraceProcessorContext* context;
+    const TraceStorage* storage;
+    SystraceSerializer serializer;
   };
 
-  SqliteRawTable(sqlite3*, Context);
-  ~SqliteRawTable() override;
-
-  static void RegisterTable(sqlite3* db, QueryCache*, TraceProcessorContext*);
-
- private:
-  void ToSystrace(sqlite3_context* ctx, int argc, sqlite3_value** argv);
-
-  SystraceSerializer serializer_;
+  static base::Status Run(Context*,
+                          size_t argc,
+                          sqlite3_value** argv,
+                          SqlValue& out,
+                          Destructors& destructors);
 };
 
 }  // namespace trace_processor
 }  // namespace perfetto
 
-#endif  // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_RAW_TABLE_H_
+#endif  // SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_TO_FTRACE_H_
diff --git a/src/trace_processor/prelude/functions/utils.h b/src/trace_processor/prelude/functions/utils.h
index 82b293f..ac848d0 100644
--- a/src/trace_processor/prelude/functions/utils.h
+++ b/src/trace_processor/prelude/functions/utils.h
@@ -19,6 +19,7 @@
 
 #include <sqlite3.h>
 #include <unordered_map>
+
 #include "perfetto/ext/base/base64.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/trace_processor/demangle.h"
@@ -26,10 +27,10 @@
 #include "src/trace_processor/export_json.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/prelude/functions/create_function_internal.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/util/status_macros.h"
 
-#include "src/trace_processor/prelude/functions/register_function.h"
-
 namespace perfetto {
 namespace trace_processor {
 
diff --git a/src/trace_processor/prelude/functions/window_functions.h b/src/trace_processor/prelude/functions/window_functions.h
index 3a76fce..a3fbeaf 100644
--- a/src/trace_processor/prelude/functions/window_functions.h
+++ b/src/trace_processor/prelude/functions/window_functions.h
@@ -28,7 +28,7 @@
 #include "src/trace_processor/prelude/functions/create_function_internal.h"
 #include "src/trace_processor/util/status_macros.h"
 
-#include "src/trace_processor/prelude/functions/register_function.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
 
 namespace perfetto {
 namespace trace_processor {
diff --git a/src/trace_processor/prelude/operators/BUILD.gn b/src/trace_processor/prelude/operators/BUILD.gn
index 2a3daa6..3d994a7 100644
--- a/src/trace_processor/prelude/operators/BUILD.gn
+++ b/src/trace_processor/prelude/operators/BUILD.gn
@@ -42,5 +42,6 @@
     "../../../../gn:default_deps",
     "../../../../gn:gtest_and_gmock",
     "../../../../gn:sqlite",
+    "../../sqlite",
   ]
 }
diff --git a/src/trace_processor/prelude/operators/span_join_operator.cc b/src/trace_processor/prelude/operators/span_join_operator.cc
index 7d4ccb8..2ee2839 100644
--- a/src/trace_processor/prelude/operators/span_join_operator.cc
+++ b/src/trace_processor/prelude/operators/span_join_operator.cc
@@ -24,6 +24,7 @@
 #include <utility>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
@@ -105,23 +106,9 @@
 
 }  // namespace
 
-SpanJoinOperatorTable::SpanJoinOperatorTable(sqlite3* db, const TraceStorage*)
+SpanJoinOperatorTable::SpanJoinOperatorTable(sqlite3* db, const void*)
     : db_(db) {}
-
-void SpanJoinOperatorTable::RegisterTable(sqlite3* db,
-                                          const TraceStorage* storage) {
-  SqliteTable::Register<SpanJoinOperatorTable>(db, storage, "span_join",
-                                               /* read_write */ false,
-                                               /* requires_args */ true);
-
-  SqliteTable::Register<SpanJoinOperatorTable>(db, storage, "span_left_join",
-                                               /* read_write */ false,
-                                               /* requires_args */ true);
-
-  SqliteTable::Register<SpanJoinOperatorTable>(db, storage, "span_outer_join",
-                                               /* read_write */ false,
-                                               /* requires_args */ true);
-}
+SpanJoinOperatorTable::~SpanJoinOperatorTable() = default;
 
 util::Status SpanJoinOperatorTable::Init(int argc,
                                          const char* const* argv,
@@ -241,7 +228,7 @@
   }
 }
 
-std::unique_ptr<SqliteTable::Cursor> SpanJoinOperatorTable::CreateCursor() {
+std::unique_ptr<SqliteTable::BaseCursor> SpanJoinOperatorTable::CreateCursor() {
   return std::unique_ptr<SpanJoinOperatorTable::Cursor>(new Cursor(this, db_));
 }
 
@@ -406,14 +393,15 @@
 }
 
 SpanJoinOperatorTable::Cursor::Cursor(SpanJoinOperatorTable* table, sqlite3* db)
-    : SqliteTable::Cursor(table),
+    : SqliteTable::BaseCursor(table),
       t1_(table, &table->t1_defn_, db),
       t2_(table, &table->t2_defn_, db),
       table_(table) {}
+SpanJoinOperatorTable::Cursor::~Cursor() = default;
 
-base::Status SpanJoinOperatorTable::Cursor::FilterInner(
-    const QueryConstraints& qc,
-    sqlite3_value** argv) {
+base::Status SpanJoinOperatorTable::Cursor::Filter(const QueryConstraints& qc,
+                                                   sqlite3_value** argv,
+                                                   FilterHistory) {
   PERFETTO_TP_TRACE(metatrace::Category::QUERY, "SPAN_JOIN_XFILTER");
 
   bool t1_partitioned_mixed =
@@ -435,7 +423,7 @@
   return FindOverlappingSpan();
 }
 
-base::Status SpanJoinOperatorTable::Cursor::NextInner() {
+base::Status SpanJoinOperatorTable::Cursor::Next() {
   RETURN_IF_ERROR(next_query_->Next());
   return FindOverlappingSpan();
 }
@@ -556,11 +544,12 @@
   return t1_less ? &t1_ : &t2_;
 }
 
-int SpanJoinOperatorTable::Cursor::Eof() {
+bool SpanJoinOperatorTable::Cursor::Eof() {
   return t1_.IsEof() || t2_.IsEof();
 }
 
-int SpanJoinOperatorTable::Cursor::Column(sqlite3_context* context, int N) {
+base::Status SpanJoinOperatorTable::Cursor::Column(sqlite3_context* context,
+                                                   int N) {
   PERFETTO_DCHECK(t1_.IsReal() || t2_.IsReal());
 
   switch (N) {
@@ -598,7 +587,7 @@
         t2_.ReportSqliteResult(context, locator.col_index);
     }
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
 SpanJoinOperatorTable::Query::Query(SpanJoinOperatorTable* table,
diff --git a/src/trace_processor/prelude/operators/span_join_operator.h b/src/trace_processor/prelude/operators/span_join_operator.h
index f150a75..3f7a006 100644
--- a/src/trace_processor/prelude/operators/span_join_operator.h
+++ b/src/trace_processor/prelude/operators/span_join_operator.h
@@ -69,7 +69,8 @@
 //
 // All other columns apart from timestamp (ts), duration (dur) and the join key
 // are passed through unchanged.
-class SpanJoinOperatorTable : public SqliteTable {
+class SpanJoinOperatorTable final
+    : public TypedSqliteTable<SpanJoinOperatorTable, const void*> {
  public:
   // Enum indicating whether the queries on the two inner tables should
   // emit shadows.
@@ -316,32 +317,17 @@
   };
 
   // Base class for a cursor on the span table.
-  class Cursor : public SqliteTable::Cursor {
+  class Cursor final : public SqliteTable::BaseCursor {
    public:
     Cursor(SpanJoinOperatorTable*, sqlite3* db);
-    ~Cursor() override = default;
+    ~Cursor() final;
 
-    int Filter(const QueryConstraints& qc,
-               sqlite3_value** argv,
-               FilterHistory) override {
-      base::Status status = FilterInner(qc, argv);
-      if (!status.ok()) {
-        table_->SetErrorMessage(sqlite3_mprintf("%s", status.c_message()));
-        return SQLITE_ERROR;
-      }
-      return SQLITE_OK;
-    }
-    int Next() override {
-      base::Status status = NextInner();
-      if (!status.ok()) {
-        table_->SetErrorMessage(sqlite3_mprintf("%s", status.c_message()));
-        return SQLITE_ERROR;
-      }
-      return SQLITE_OK;
-    }
-
-    int Column(sqlite3_context* context, int N) override;
-    int Eof() override;
+    base::Status Filter(const QueryConstraints& qc,
+                        sqlite3_value** argv,
+                        FilterHistory);
+    base::Status Next();
+    base::Status Column(sqlite3_context* context, int N);
+    bool Eof();
 
    private:
     Cursor(Cursor&) = delete;
@@ -351,11 +337,7 @@
     Cursor& operator=(Cursor&&) = default;
 
     bool IsOverlappingSpan();
-
-    base::Status NextInner();
-    base::Status FilterInner(const QueryConstraints& qc, sqlite3_value** argv);
     util::Status FindOverlappingSpan();
-
     Query* FindEarliestFinishQuery();
 
     Query t1_;
@@ -369,15 +351,14 @@
     SpanJoinOperatorTable* table_;
   };
 
-  SpanJoinOperatorTable(sqlite3*, const TraceStorage*);
-
-  static void RegisterTable(sqlite3* db, const TraceStorage* storage);
+  SpanJoinOperatorTable(sqlite3*, const void*);
+  ~SpanJoinOperatorTable() final;
 
   // Table implementation.
-  util::Status Init(int, const char* const*, SqliteTable::Schema*) override;
-  std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
-  int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) override;
-  int FindFunction(const char* name, FindFunctionFn* fn, void** args) override;
+  util::Status Init(int, const char* const*, SqliteTable::Schema*) final;
+  std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
+  int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) final;
+  int FindFunction(const char* name, FindFunctionFn* fn, void** args) final;
 
  private:
   // Columns of the span operator table.
diff --git a/src/trace_processor/prelude/operators/span_join_operator_unittest.cc b/src/trace_processor/prelude/operators/span_join_operator_unittest.cc
index 737e828..661a7f1 100644
--- a/src/trace_processor/prelude/operators/span_join_operator_unittest.cc
+++ b/src/trace_processor/prelude/operators/span_join_operator_unittest.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/prelude/operators/span_join_operator.h"
 
+#include "src/trace_processor/sqlite/sqlite_engine.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
@@ -25,19 +26,19 @@
 class SpanJoinOperatorTableTest : public ::testing::Test {
  public:
   SpanJoinOperatorTableTest() {
-    sqlite3* db = nullptr;
-    PERFETTO_CHECK(sqlite3_initialize() == SQLITE_OK);
-    PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
-    db_.reset(db);
-
-    SpanJoinOperatorTable::RegisterTable(db_.get(), nullptr);
+    engine_.RegisterVirtualTableModule<SpanJoinOperatorTable>(
+        "span_join", nullptr, SqliteTable::TableType::kExplicitCreate, false);
+    engine_.RegisterVirtualTableModule<SpanJoinOperatorTable>(
+        "span_left_join", nullptr, SqliteTable::TableType::kExplicitCreate,
+        false);
   }
 
   void PrepareValidStatement(const std::string& sql) {
     int size = static_cast<int>(sql.size());
     sqlite3_stmt* stmt;
-    ASSERT_EQ(sqlite3_prepare_v2(*db_, sql.c_str(), size, &stmt, nullptr),
-              SQLITE_OK);
+    ASSERT_EQ(
+        sqlite3_prepare_v2(engine_.db(), sql.c_str(), size, &stmt, nullptr),
+        SQLITE_OK);
     stmt_.reset(stmt);
   }
 
@@ -55,7 +56,7 @@
   }
 
  protected:
-  ScopedDb db_;
+  SqliteEngine engine_;
   ScopedStmt stmt_;
 };
 
diff --git a/src/trace_processor/prelude/operators/window_operator.cc b/src/trace_processor/prelude/operators/window_operator.cc
index b308c53..68f2ff4 100644
--- a/src/trace_processor/prelude/operators/window_operator.cc
+++ b/src/trace_processor/prelude/operators/window_operator.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/prelude/operators/window_operator.h"
 
+#include "perfetto/base/status.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 
 namespace perfetto {
@@ -26,11 +27,7 @@
 }  // namespace
 
 WindowOperatorTable::WindowOperatorTable(sqlite3*, const TraceStorage*) {}
-
-void WindowOperatorTable::RegisterTable(sqlite3* db,
-                                        const TraceStorage* storage) {
-  SqliteTable::Register<WindowOperatorTable>(db, storage, "window", true);
-}
+WindowOperatorTable::~WindowOperatorTable() = default;
 
 base::Status WindowOperatorTable::Init(int,
                                        const char* const*,
@@ -57,54 +54,55 @@
   return base::OkStatus();
 }
 
-std::unique_ptr<SqliteTable::Cursor> WindowOperatorTable::CreateCursor() {
-  return std::unique_ptr<SqliteTable::Cursor>(new Cursor(this));
+std::unique_ptr<SqliteTable::BaseCursor> WindowOperatorTable::CreateCursor() {
+  return std::unique_ptr<SqliteTable::BaseCursor>(new Cursor(this));
 }
 
 int WindowOperatorTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
   return SQLITE_OK;
 }
 
-int WindowOperatorTable::ModifyConstraints(QueryConstraints* qc) {
+base::Status WindowOperatorTable::ModifyConstraints(QueryConstraints* qc) {
   // Remove ordering on timestamp if it is the only ordering as we are already
   // sorted on TS. This makes span joining significantly faster.
   const auto& ob = qc->order_by();
   if (ob.size() == 1 && ob[0].iColumn == Column::kTs && !ob[0].desc) {
     qc->mutable_order_by()->clear();
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int WindowOperatorTable::Update(int argc,
-                                sqlite3_value** argv,
-                                sqlite3_int64*) {
+base::Status WindowOperatorTable::Update(int argc,
+                                         sqlite3_value** argv,
+                                         sqlite3_int64*) {
   // We only support updates to ts and dur. Disallow deletes (argc == 1) and
   // inserts (argv[0] == null).
-  if (argc < 2 || sqlite3_value_type(argv[0]) == SQLITE_NULL)
-    return SQLITE_READONLY;
+  if (argc < 2 || sqlite3_value_type(argv[0]) == SQLITE_NULL) {
+    return base::ErrStatus(
+        "Invalid number/value of arguments when updating window table");
+  }
 
   int64_t new_quantum = sqlite3_value_int64(argv[3]);
   int64_t new_start = sqlite3_value_int64(argv[4]);
   int64_t new_dur = sqlite3_value_int64(argv[5]);
   if (new_dur == 0) {
-    auto* err = sqlite3_mprintf("Cannot set duration of window table to zero.");
-    SetErrorMessage(err);
-    return SQLITE_ERROR;
+    return base::ErrStatus("Cannot set duration of window table to zero.");
   }
 
   quantum_ = new_quantum;
   window_start_ = new_start;
   window_dur_ = new_dur;
 
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
 WindowOperatorTable::Cursor::Cursor(WindowOperatorTable* table)
-    : SqliteTable::Cursor(table), table_(table) {}
+    : SqliteTable::BaseCursor(table), table_(table) {}
+WindowOperatorTable::Cursor::~Cursor() = default;
 
-int WindowOperatorTable::Cursor::Filter(const QueryConstraints& qc,
-                                        sqlite3_value** argv,
-                                        FilterHistory) {
+base::Status WindowOperatorTable::Cursor::Filter(const QueryConstraints& qc,
+                                                 sqlite3_value** argv,
+                                                 FilterHistory) {
   *this = Cursor(table_);
   window_start_ = table_->window_start_;
   window_end_ = table_->window_start_ + table_->window_dur_;
@@ -123,10 +121,11 @@
   } else {
     filter_type_ = FilterType::kReturnAll;
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int WindowOperatorTable::Cursor::Column(sqlite3_context* context, int N) {
+base::Status WindowOperatorTable::Cursor::Column(sqlite3_context* context,
+                                                 int N) {
   switch (N) {
     case Column::kQuantum: {
       sqlite3_result_int64(context,
@@ -163,10 +162,10 @@
       break;
     }
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int WindowOperatorTable::Cursor::Next() {
+base::Status WindowOperatorTable::Cursor::Next() {
   switch (filter_type_) {
     case FilterType::kReturnFirst:
       current_ts_ = window_end_;
@@ -177,10 +176,10 @@
       break;
   }
   row_id_++;
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int WindowOperatorTable::Cursor::Eof() {
+bool WindowOperatorTable::Cursor::Eof() {
   return current_ts_ >= window_end_;
 }
 
diff --git a/src/trace_processor/prelude/operators/window_operator.h b/src/trace_processor/prelude/operators/window_operator.h
index d10b81b..5f4af16 100644
--- a/src/trace_processor/prelude/operators/window_operator.h
+++ b/src/trace_processor/prelude/operators/window_operator.h
@@ -20,6 +20,7 @@
 #include <limits>
 #include <memory>
 
+#include "perfetto/base/status.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 
 namespace perfetto {
@@ -27,7 +28,8 @@
 
 class TraceStorage;
 
-class WindowOperatorTable : public SqliteTable {
+class WindowOperatorTable final
+    : public TypedSqliteTable<WindowOperatorTable, const TraceStorage*> {
  public:
   enum Column {
     kRowId = 0,
@@ -38,17 +40,21 @@
     kDuration = 5,
     kQuantumTs = 6
   };
-  class Cursor : public SqliteTable::Cursor {
+  class Cursor final : public SqliteTable::BaseCursor {
    public:
     explicit Cursor(WindowOperatorTable*);
+    ~Cursor() final;
+
+    Cursor(Cursor&&) = default;
+    Cursor& operator=(Cursor&&) = default;
 
     // Implementation of SqliteTable::Cursor.
-    int Filter(const QueryConstraints& qc,
-               sqlite3_value**,
-               FilterHistory) override;
-    int Next() override;
-    int Eof() override;
-    int Column(sqlite3_context*, int N) override;
+    base::Status Filter(const QueryConstraints& qc,
+                        sqlite3_value**,
+                        FilterHistory);
+    base::Status Next();
+    bool Eof();
+    base::Status Column(sqlite3_context*, int N);
 
    private:
     // Defines the data to be generated by the table.
@@ -72,16 +78,15 @@
     WindowOperatorTable* table_ = nullptr;
   };
 
-  static void RegisterTable(sqlite3* db, const TraceStorage* storage);
-
   WindowOperatorTable(sqlite3*, const TraceStorage*);
+  ~WindowOperatorTable() final;
 
   // Table implementation.
-  base::Status Init(int, const char* const*, Schema* schema) override;
-  std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
-  int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
-  int ModifyConstraints(QueryConstraints* qc) override;
-  int Update(int, sqlite3_value**, sqlite3_int64*) override;
+  base::Status Init(int, const char* const*, Schema* schema) final;
+  std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
+  int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
+  base::Status ModifyConstraints(QueryConstraints* qc) final;
+  base::Status Update(int, sqlite3_value**, sqlite3_int64*) final;
 
  private:
   int64_t quantum_ = 0;
@@ -91,6 +96,7 @@
   // uint64s.
   int64_t window_dur_ = std::numeric_limits<int64_t>::max();
 };
+
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/prelude/table_functions/BUILD.gn b/src/trace_processor/prelude/table_functions/BUILD.gn
index 4f5ced3..2a97bca 100644
--- a/src/trace_processor/prelude/table_functions/BUILD.gn
+++ b/src/trace_processor/prelude/table_functions/BUILD.gn
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import("../../../../gn/perfetto.gni")
+import("../../../../gn/perfetto_tp_tables.gni")
 
 assert(enable_perfetto_trace_processor_sqlite)
 
@@ -38,12 +39,11 @@
     "experimental_slice_layout.h",
     "flamegraph_construction_algorithms.cc",
     "flamegraph_construction_algorithms.h",
-    "table_function.cc",
-    "table_function.h",
     "view.cc",
     "view.h",
   ]
   deps = [
+    ":tables",
     "../../../../gn:default_deps",
     "../../../../gn:sqlite",
     "../../../base",
@@ -51,12 +51,31 @@
     "../../db",
     "../../importers/proto:full",
     "../../importers/proto:minimal",
-    "../../sqlite:sqlite_minimal",
+    "../../sqlite",
     "../../storage",
     "../../tables",
     "../../types",
     "../../util",
   ]
+  public_deps = [ ":interface" ]
+}
+
+source_set("interface") {
+  sources = [
+    "table_function.cc",
+    "table_function.h",
+  ]
+  deps = [
+    "../../../../gn:default_deps",
+    "../../../base",
+    "../../db",
+    "../../sqlite:query_constraints",
+  ]
+}
+
+perfetto_tp_tables("tables") {
+  sources = [ "tables.py" ]
+  deps = [ "../../tables:tables_python" ]
 }
 
 source_set("unittests") {
@@ -71,6 +90,7 @@
   ]
   deps = [
     ":table_functions",
+    ":tables",
     "../../../../gn:default_deps",
     "../../../../gn:gtest_and_gmock",
     "../../containers",
diff --git a/src/trace_processor/prelude/table_functions/ancestor.cc b/src/trace_processor/prelude/table_functions/ancestor.cc
index c1656d9..9089b82 100644
--- a/src/trace_processor/prelude/table_functions/ancestor.cc
+++ b/src/trace_processor/prelude/table_functions/ancestor.cc
@@ -19,6 +19,7 @@
 #include <memory>
 #include <set>
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/status_macros.h"
diff --git a/src/trace_processor/prelude/table_functions/ancestor.h b/src/trace_processor/prelude/table_functions/ancestor.h
index c625f16..755a482 100644
--- a/src/trace_processor/prelude/table_functions/ancestor.h
+++ b/src/trace_processor/prelude/table_functions/ancestor.h
@@ -16,40 +16,14 @@
 
 #ifndef SRC_TRACE_PROCESSOR_PRELUDE_TABLE_FUNCTIONS_ANCESTOR_H_
 #define SRC_TRACE_PROCESSOR_PRELUDE_TABLE_FUNCTIONS_ANCESTOR_H_
+
 #include <optional>
 
 #include "src/trace_processor/prelude/table_functions/table_function.h"
 #include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/profiler_tables.h"
-#include "src/trace_processor/tables/slice_tables.h"
 
 namespace perfetto {
 namespace trace_processor {
-namespace tables {
-
-#define PERFETTO_TP_ANCESTOR_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(AncestorSliceTable, "ancestor_slice")                  \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                      \
-  C(tables::SliceTable::Id, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_ANCESTOR_SLICE_TABLE_DEF);
-
-#define PERFETTO_TP_ANCESTOR_STACK_PROFILE_CALLSITE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(AncestorStackProfileCallsiteTable,                                      \
-       "experimental_ancestor_stack_profile_callsite")                         \
-  PARENT(PERFETTO_TP_STACK_PROFILE_CALLSITE_DEF, C)                            \
-  C(tables::StackProfileCallsiteTable::Id, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_ANCESTOR_STACK_PROFILE_CALLSITE_TABLE_DEF);
-
-#define PERFETTO_TP_ANCESTOR_SLICE_BY_STACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(AncestorSliceByStackTable, "ancestor_slice_by_stack")           \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                               \
-  C(int64_t, start_stack_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_ANCESTOR_SLICE_BY_STACK_TABLE_DEF);
-
-}  // namespace tables
 
 class TraceProcessorContext;
 
diff --git a/src/trace_processor/prelude/table_functions/ancestor_unittest.cc b/src/trace_processor/prelude/table_functions/ancestor_unittest.cc
index 08fdad4..afc2d33 100644
--- a/src/trace_processor/prelude/table_functions/ancestor_unittest.cc
+++ b/src/trace_processor/prelude/table_functions/ancestor_unittest.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/prelude/table_functions/ancestor.h"
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/prelude/table_functions/connected_flow.h b/src/trace_processor/prelude/table_functions/connected_flow.h
index c05e25e..3ef3fc3 100644
--- a/src/trace_processor/prelude/table_functions/connected_flow.h
+++ b/src/trace_processor/prelude/table_functions/connected_flow.h
@@ -18,24 +18,14 @@
 #define SRC_TRACE_PROCESSOR_PRELUDE_TABLE_FUNCTIONS_CONNECTED_FLOW_H_
 
 #include "src/trace_processor/prelude/table_functions/table_function.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/flow_tables.h"
 
 #include <queue>
 #include <set>
 
 namespace perfetto {
 namespace trace_processor {
-namespace tables {
-
-#define PERFETTO_TP_CONNECTED_FLOW_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ConnectedFlowTable, "not_exposed_to_sql")              \
-  PARENT(PERFETTO_TP_FLOW_TABLE_DEF, C)                       \
-  C(uint32_t, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_CONNECTED_FLOW_TABLE_DEF);
-
-}  // namespace tables
 
 class TraceProcessorContext;
 
diff --git a/src/trace_processor/prelude/table_functions/descendant.cc b/src/trace_processor/prelude/table_functions/descendant.cc
index 75cb8c9..382edf2 100644
--- a/src/trace_processor/prelude/table_functions/descendant.cc
+++ b/src/trace_processor/prelude/table_functions/descendant.cc
@@ -19,6 +19,7 @@
 #include <memory>
 #include <set>
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/status_macros.h"
diff --git a/src/trace_processor/prelude/table_functions/descendant.h b/src/trace_processor/prelude/table_functions/descendant.h
index 8e93811..bffd9b5 100644
--- a/src/trace_processor/prelude/table_functions/descendant.h
+++ b/src/trace_processor/prelude/table_functions/descendant.h
@@ -24,23 +24,6 @@
 
 namespace perfetto {
 namespace trace_processor {
-namespace tables {
-
-#define PERFETTO_TP_DESCENDANT_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(DescendantSliceTable, "descendant_slice")                \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                        \
-  C(uint32_t, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_DESCENDANT_SLICE_TABLE_DEF);
-
-#define PERFETTO_TP_DESCENDANT_SLICE_BY_STACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(DescendantSliceByStackTable, "descendant_slice_by_stack")         \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                                 \
-  C(int64_t, start_stack_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_DESCENDANT_SLICE_BY_STACK_TABLE_DEF);
-
-}  // namespace tables
 
 class TraceProcessorContext;
 
diff --git a/src/trace_processor/prelude/table_functions/descendant_unittest.cc b/src/trace_processor/prelude/table_functions/descendant_unittest.cc
index f1ef1ca..67399d1 100644
--- a/src/trace_processor/prelude/table_functions/descendant_unittest.cc
+++ b/src/trace_processor/prelude/table_functions/descendant_unittest.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/prelude/table_functions/descendant.h"
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/prelude/table_functions/experimental_annotated_stack.cc b/src/trace_processor/prelude/table_functions/experimental_annotated_stack.cc
index 8198376..09363f2 100644
--- a/src/trace_processor/prelude/table_functions/experimental_annotated_stack.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_annotated_stack.cc
@@ -19,24 +19,15 @@
 #include <optional>
 
 #include "perfetto/ext/base/string_utils.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/storage/trace_storage.h"
-#include "src/trace_processor/tables/profiler_tables.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace tables {
 
-#define PERFETTO_TP_ANNOTATED_CALLSTACK_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ExperimentalAnnotatedCallstackTable,                        \
-       "experimental_annotated_callstack")                         \
-  PARENT(PERFETTO_TP_STACK_PROFILE_CALLSITE_DEF, C)                \
-  C(StringId, annotation)                                          \
-  C(tables::StackProfileCallsiteTable::Id, start_id, Column::Flag::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_ANNOTATED_CALLSTACK_TABLE_DEF);
-
 ExperimentalAnnotatedCallstackTable::~ExperimentalAnnotatedCallstackTable() =
     default;
 
diff --git a/src/trace_processor/prelude/table_functions/experimental_counter_dur.cc b/src/trace_processor/prelude/table_functions/experimental_counter_dur.cc
index 3513c89..a7d9814 100644
--- a/src/trace_processor/prelude/table_functions/experimental_counter_dur.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_counter_dur.cc
@@ -16,20 +16,12 @@
 
 #include "src/trace_processor/prelude/table_functions/experimental_counter_dur.h"
 
-#include "src/trace_processor/tables/counter_tables.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace tables {
 
-#define PERFETTO_TP_COUNTER_DUR_TABLE_DEF(NAME, PARENT, C)      \
-  NAME(ExperimentalCounterDurTable, "experimental_counter_dur") \
-  PARENT(PERFETTO_TP_COUNTER_TABLE_DEF, C)                      \
-  C(int64_t, dur)                                               \
-  C(double, delta)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_COUNTER_DUR_TABLE_DEF);
-
 ExperimentalCounterDurTable::~ExperimentalCounterDurTable() = default;
 
 }  // namespace tables
diff --git a/src/trace_processor/prelude/table_functions/experimental_sched_upid.cc b/src/trace_processor/prelude/table_functions/experimental_sched_upid.cc
index 45a76ea..ba3a0c6 100644
--- a/src/trace_processor/prelude/table_functions/experimental_sched_upid.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_sched_upid.cc
@@ -16,17 +16,14 @@
 
 #include "src/trace_processor/prelude/table_functions/experimental_sched_upid.h"
 
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
+
 namespace perfetto {
 namespace trace_processor {
 namespace tables {
-#define PERFETTO_TP_SCHED_UPID_TABLE_DEF(NAME, PARENT, C)     \
-  NAME(ExperimentalSchedUpidTable, "experimental_sched_upid") \
-  PARENT(PERFETTO_TP_SCHED_SLICE_TABLE_DEF, C)                \
-  C(std::optional<UniquePid>, upid)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_SCHED_UPID_TABLE_DEF);
 
 ExperimentalSchedUpidTable::~ExperimentalSchedUpidTable() = default;
+
 }  // namespace tables
 
 ExperimentalSchedUpid::ExperimentalSchedUpid(
diff --git a/src/trace_processor/prelude/table_functions/experimental_slice_layout.cc b/src/trace_processor/prelude/table_functions/experimental_slice_layout.cc
index e544dbb..d6276dc 100644
--- a/src/trace_processor/prelude/table_functions/experimental_slice_layout.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_slice_layout.cc
@@ -20,11 +20,13 @@
 
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 
 namespace perfetto {
 namespace trace_processor {
 namespace tables {
+
 ExperimentalSliceLayoutTable::~ExperimentalSliceLayoutTable() = default;
 }
 
diff --git a/src/trace_processor/prelude/table_functions/experimental_slice_layout.h b/src/trace_processor/prelude/table_functions/experimental_slice_layout.h
index 19af803..3aa61bb 100644
--- a/src/trace_processor/prelude/table_functions/experimental_slice_layout.h
+++ b/src/trace_processor/prelude/table_functions/experimental_slice_layout.h
@@ -25,18 +25,6 @@
 namespace perfetto {
 namespace trace_processor {
 
-namespace tables {
-
-#define PERFETTO_TP_SLICE_LAYOUT_TABLE_DEF(NAME, PARENT, C)       \
-  NAME(ExperimentalSliceLayoutTable, "experimental_slice_layout") \
-  PARENT(PERFETTO_TP_SLICE_TABLE_DEF, C)                          \
-  C(uint32_t, layout_depth)                                       \
-  C(StringPool::Id, filter_track_ids, Column::kHidden)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_SLICE_LAYOUT_TABLE_DEF);
-
-}  // namespace tables
-
 class ExperimentalSliceLayout : public TableFunction {
  public:
   ExperimentalSliceLayout(StringPool* string_pool,
diff --git a/src/trace_processor/prelude/table_functions/experimental_slice_layout_unittest.cc b/src/trace_processor/prelude/table_functions/experimental_slice_layout_unittest.cc
index 43c986d..164dfbb 100644
--- a/src/trace_processor/prelude/table_functions/experimental_slice_layout_unittest.cc
+++ b/src/trace_processor/prelude/table_functions/experimental_slice_layout_unittest.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 
 #include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/prelude/table_functions/tables_py.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
diff --git a/src/trace_processor/prelude/table_functions/tables.py b/src/trace_processor/prelude/table_functions/tables.py
new file mode 100644
index 0000000..4ffc526
--- /dev/null
+++ b/src/trace_processor/prelude/table_functions/tables.py
@@ -0,0 +1,166 @@
+# 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.
+"""Contains tables for finding ancestor events."""
+
+from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnFlag
+from python.generators.trace_processor_table.public import CppDouble
+from python.generators.trace_processor_table.public import CppInt64
+from python.generators.trace_processor_table.public import CppOptional
+from python.generators.trace_processor_table.public import CppString
+from python.generators.trace_processor_table.public import CppTableId
+from python.generators.trace_processor_table.public import CppUint32
+from python.generators.trace_processor_table.public import Table
+
+from src.trace_processor.tables.counter_tables import COUNTER_TABLE
+from src.trace_processor.tables.flow_tables import FLOW_TABLE
+from src.trace_processor.tables.metadata_tables import PROCESS_TABLE
+from src.trace_processor.tables.profiler_tables import STACK_PROFILE_CALLSITE_TABLE
+from src.trace_processor.tables.slice_tables import SLICE_TABLE
+from src.trace_processor.tables.slice_tables import SCHED_SLICE_TABLE
+
+ANCESTOR_SLICE_TABLE = Table(
+    python_module=__file__,
+    class_name="AncestorSliceTable",
+    sql_name="ancestor_slice",
+    columns=[
+        C("start_id", CppTableId(SLICE_TABLE), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+ANCESTOR_SLICE_BY_STACK_TABLE = Table(
+    python_module=__file__,
+    class_name="AncestorSliceByStackTable",
+    sql_name="ancestor_slice_by_stack",
+    columns=[
+        C("start_stack_id", CppInt64(), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+ANCESTOR_STACK_PROFILE_CALLSITE_TABLE = Table(
+    python_module=__file__,
+    class_name="AncestorStackProfileCallsiteTable",
+    sql_name="experimental_ancestor_stack_profile_callsite",
+    columns=[
+        C("start_id",
+          CppTableId(STACK_PROFILE_CALLSITE_TABLE),
+          flags=ColumnFlag.HIDDEN),
+    ],
+    parent=STACK_PROFILE_CALLSITE_TABLE)
+
+CONNECTED_FLOW_TABLE = Table(
+    python_module=__file__,
+    class_name="ConnectedFlowTable",
+    sql_name="not_exposed_to_sql",
+    columns=[
+        C("start_id", CppTableId(SLICE_TABLE), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=FLOW_TABLE)
+
+DESCENDANT_SLICE_TABLE = Table(
+    python_module=__file__,
+    class_name="DescendantSliceTable",
+    sql_name="descendant_slice",
+    columns=[
+        C("start_id", CppTableId(SLICE_TABLE), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+DESCENDANT_SLICE_BY_STACK_TABLE = Table(
+    python_module=__file__,
+    class_name="DescendantSliceByStackTable",
+    sql_name="descendant_slice_by_stack",
+    columns=[
+        C("start_stack_id", CppInt64(), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalAnnotatedCallstackTable",
+    sql_name="experimental_annotated_callstack",
+    columns=[
+        C("annotation", CppString()),
+        C("start_id",
+          CppTableId(STACK_PROFILE_CALLSITE_TABLE),
+          flags=ColumnFlag.HIDDEN),
+    ],
+    parent=STACK_PROFILE_CALLSITE_TABLE)
+
+EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalAnnotatedCallstackTable",
+    sql_name="experimental_annotated_callstack",
+    columns=[
+        C("annotation", CppString()),
+        C("start_id",
+          CppTableId(STACK_PROFILE_CALLSITE_TABLE),
+          flags=ColumnFlag.HIDDEN),
+    ],
+    parent=STACK_PROFILE_CALLSITE_TABLE)
+
+EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalAnnotatedCallstackTable",
+    sql_name="experimental_annotated_callstack",
+    columns=[
+        C("annotation", CppString()),
+        C("start_id",
+          CppTableId(STACK_PROFILE_CALLSITE_TABLE),
+          flags=ColumnFlag.HIDDEN),
+    ],
+    parent=STACK_PROFILE_CALLSITE_TABLE)
+
+EXPERIMENTAL_COUNTER_DUR_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalCounterDurTable",
+    sql_name="experimental_counter_dur",
+    columns=[
+        C("dur", CppInt64()),
+        C("delta", CppDouble()),
+    ],
+    parent=COUNTER_TABLE)
+
+EXPERIMENTAL_SCHED_UPID_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalSchedUpidTable",
+    sql_name="experimental_sched_upid",
+    columns=[
+        C("upid", CppOptional(CppTableId(PROCESS_TABLE))),
+    ],
+    parent=SCHED_SLICE_TABLE)
+
+EXPERIMENTAL_SLICE_LAYOUT_TABLE = Table(
+    python_module=__file__,
+    class_name="ExperimentalSliceLayoutTable",
+    sql_name="experimental_slice_layout",
+    columns=[
+        C("layout_depth", CppUint32()),
+        C("filter_track_ids", CppString(), flags=ColumnFlag.HIDDEN),
+    ],
+    parent=SLICE_TABLE)
+
+# Keep this list sorted.
+ALL_TABLES = [
+    ANCESTOR_SLICE_BY_STACK_TABLE,
+    ANCESTOR_SLICE_TABLE,
+    ANCESTOR_STACK_PROFILE_CALLSITE_TABLE,
+    CONNECTED_FLOW_TABLE,
+    DESCENDANT_SLICE_BY_STACK_TABLE,
+    DESCENDANT_SLICE_TABLE,
+    EXPERIMENTAL_ANNOTATED_CALLSTACK_TABLE,
+    EXPERIMENTAL_COUNTER_DUR_TABLE,
+    EXPERIMENTAL_SCHED_UPID_TABLE,
+    EXPERIMENTAL_SLICE_LAYOUT_TABLE,
+]
diff --git a/src/trace_processor/prelude/tables_views/BUILD.gn b/src/trace_processor/prelude/tables_views/BUILD.gn
new file mode 100644
index 0000000..110d0b9
--- /dev/null
+++ b/src/trace_processor/prelude/tables_views/BUILD.gn
@@ -0,0 +1,31 @@
+# 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.
+
+import("../../../../gn/perfetto.gni")
+import("../../../../gn/perfetto_sql.gni")
+
+assert(enable_perfetto_trace_processor_sqlite)
+
+perfetto_amalgamated_sql_header("tables_views") {
+  deps = [ ":sources" ]
+  generated_header = "tables_views.h"
+  namespace = "prelude::tables_views"
+}
+
+perfetto_sql_source_set("sources") {
+  sources = [
+    "tables.sql",
+    "views.sql",
+  ]
+}
diff --git a/src/trace_processor/prelude/tables_views/tables.sql b/src/trace_processor/prelude/tables_views/tables.sql
new file mode 100644
index 0000000..28ea0f5
--- /dev/null
+++ b/src/trace_processor/prelude/tables_views/tables.sql
@@ -0,0 +1,25 @@
+CREATE TABLE perfetto_tables(name STRING);
+
+CREATE TABLE trace_bounds AS
+SELECT 0 AS start_ts, 0 AS end_ts;
+
+CREATE TABLE power_profile(
+  device STRING,
+  cpu INT,
+  cluster INT,
+  freq INT,
+  power DOUBLE,
+  UNIQUE(device, cpu, cluster, freq)
+);
+
+CREATE TABLE trace_metrics(name STRING);
+
+CREATE TABLE debug_slices(
+  id BIGINT,
+  name STRING,
+  ts BIGINT,
+  dur BIGINT,
+  depth BIGINT
+);
+
+CREATE VIRTUAL TABLE window USING window();
diff --git a/src/trace_processor/prelude/tables_views/views.sql b/src/trace_processor/prelude/tables_views/views.sql
new file mode 100644
index 0000000..6962bae
--- /dev/null
+++ b/src/trace_processor/prelude/tables_views/views.sql
@@ -0,0 +1,55 @@
+CREATE VIEW counters AS 
+SELECT *
+FROM counter v 
+JOIN counter_track t ON v.track_id = t.id 
+ORDER BY ts;
+
+CREATE VIEW slice AS 
+SELECT
+  *, 
+  category AS cat, 
+  id AS slice_id 
+FROM internal_slice;
+
+CREATE VIEW instant AS 
+SELECT ts, track_id, name, arg_set_id 
+FROM slice 
+WHERE dur = 0;
+
+CREATE VIEW sched AS 
+SELECT 
+  *,
+  ts + dur as ts_end
+FROM sched_slice;
+
+CREATE VIEW slices AS 
+SELECT * FROM slice;
+
+CREATE VIEW thread AS 
+SELECT 
+  id as utid,
+  *
+FROM internal_thread;
+
+CREATE VIEW process AS 
+SELECT 
+  id as upid,
+  * 
+FROM internal_process;
+
+-- This should be kept in sync with GlobalArgsTracker::AddArgSet.
+CREATE VIEW args AS 
+SELECT 
+  *,
+  CASE value_type
+    WHEN 'int' THEN CAST(int_value AS text)
+    WHEN 'uint' THEN CAST(int_value AS text)
+    WHEN 'string' THEN string_value
+    WHEN 'real' THEN CAST(real_value AS text)
+    WHEN 'pointer' THEN printf('0x%x', int_value)
+    WHEN 'bool' THEN (
+      CASE WHEN int_value <> 0 THEN 'true'
+      ELSE 'false' END)
+    WHEN 'json' THEN string_value
+  ELSE NULL END AS display_value
+FROM internal_args;
diff --git a/src/trace_processor/read_trace_integrationtest.cc b/src/trace_processor/read_trace_integrationtest.cc
index 439f1fa..818eb6d 100644
--- a/src/trace_processor/read_trace_integrationtest.cc
+++ b/src/trace_processor/read_trace_integrationtest.cc
@@ -46,7 +46,23 @@
   return raw_trace;
 }
 
-TEST(ReadTraceIntegrationTest, CompressedTrace) {
+bool ZlibSupported() {
+#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+  return true;
+#else
+  return false;
+#endif
+}
+
+class ReadTraceIntegrationTest : public testing::Test {
+  void SetUp() override {
+    if (!ZlibSupported()) {
+      GTEST_SKIP() << "Gzip not enabled";
+    }
+  }
+};
+
+TEST_F(ReadTraceIntegrationTest, CompressedTrace) {
   base::ScopedFstream f = OpenTestTrace("test/data/compressed.pb");
   std::vector<uint8_t> raw_trace = ReadAllData(f);
 
@@ -68,7 +84,7 @@
   ASSERT_EQ(packet_count, 2412u);
 }
 
-TEST(ReadTraceIntegrationTest, NonProtobufShouldNotDecompress) {
+TEST_F(ReadTraceIntegrationTest, NonProtobufShouldNotDecompress) {
   base::ScopedFstream f = OpenTestTrace("test/data/unsorted_trace.json");
   std::vector<uint8_t> raw_trace = ReadAllData(f);
 
@@ -78,7 +94,7 @@
   ASSERT_FALSE(status.ok());
 }
 
-TEST(ReadTraceIntegrationTest, OuterGzipDecompressTrace) {
+TEST_F(ReadTraceIntegrationTest, OuterGzipDecompressTrace) {
   base::ScopedFstream f =
       OpenTestTrace("test/data/example_android_trace_30s.pb.gz");
   std::vector<uint8_t> raw_compressed_trace = ReadAllData(f);
@@ -96,7 +112,7 @@
   ASSERT_EQ(decompressed, raw_trace);
 }
 
-TEST(ReadTraceIntegrationTest, DoubleGzipDecompressTrace) {
+TEST_F(ReadTraceIntegrationTest, DoubleGzipDecompressTrace) {
   base::ScopedFstream f = OpenTestTrace("test/data/compressed.pb.gz");
   std::vector<uint8_t> raw_compressed_trace = ReadAllData(f);
 
diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn
index 21ed49a..7045815 100644
--- a/src/trace_processor/sqlite/BUILD.gn
+++ b/src/trace_processor/sqlite/BUILD.gn
@@ -21,48 +21,48 @@
     "db_sqlite_table.cc",
     "db_sqlite_table.h",
     "query_cache.h",
+    "scoped_db.h",
     "sql_stats_table.cc",
     "sql_stats_table.h",
-    "sqlite_raw_table.cc",
-    "sqlite_raw_table.h",
+    "sqlite_engine.cc",
+    "sqlite_engine.h",
+    "sqlite_table.cc",
+    "sqlite_table.h",
     "sqlite_utils.cc",
     "sqlite_utils.h",
+    "sqlite_utils.h",
     "stats_table.cc",
     "stats_table.h",
   ]
   deps = [
+    ":query_constraints",
     "..:metatrace",
     "../../../gn:default_deps",
     "../../../gn:sqlite",
+    "../../../include/perfetto/trace_processor",
     "../../../protos/perfetto/trace/ftrace:zero",
     "../../base",
     "../containers",
     "../db",
     "../importers/common",
     "../importers/ftrace:ftrace_descriptors",
-    "../prelude/table_functions",
+    "../prelude/functions:interface",
+    "../prelude/table_functions:interface",
     "../storage",
     "../types",
     "../util",
     "../util:profile_builder",
   ]
-  public_deps = [ ":sqlite_minimal" ]
 }
 
-source_set("sqlite_minimal") {
+source_set("query_constraints") {
   sources = [
     "query_constraints.cc",
     "query_constraints.h",
-    "scoped_db.h",
-    "sqlite_table.cc",
-    "sqlite_table.h",
-    "sqlite_utils.h",
   ]
   deps = [
-    "..:metatrace",
     "../../../gn:default_deps",
     "../../../gn:sqlite",
-    "../../../include/perfetto/trace_processor",
     "../../base",
   ]
 }
@@ -75,8 +75,8 @@
     "sqlite_utils_unittest.cc",
   ]
   deps = [
+    ":query_constraints",
     ":sqlite",
-    ":sqlite_minimal",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
     "../../../gn:sqlite",
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index a97ec8f..f73c5c1 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/sqlite/db_sqlite_table.h"
 
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/small_vector.h"
 #include "perfetto/ext/base/string_writer.h"
 #include "src/trace_processor/containers/bit_vector.h"
@@ -134,30 +135,6 @@
       generator_(std::move(context.generator)) {}
 DbSqliteTable::~DbSqliteTable() = default;
 
-void DbSqliteTable::RegisterTable(sqlite3* db,
-                                  QueryCache* cache,
-                                  const Table* table,
-                                  const std::string& name) {
-  Context context{cache, TableComputation::kStatic, table, nullptr};
-  SqliteTable::Register<DbSqliteTable, Context>(db, std::move(context), name);
-}
-
-void DbSqliteTable::RegisterTable(sqlite3* db,
-                                  QueryCache* cache,
-                                  std::unique_ptr<TableFunction> generator) {
-  // Figure out if the table needs explicit args (in the form of constraints
-  // on hidden columns) passed to it in order to make the query valid.
-  base::Status status = generator->ValidateConstraints(
-      QueryConstraints(std::numeric_limits<uint64_t>::max()));
-  bool requires_args = !status.ok();
-
-  std::string table_name = generator->TableName();
-  Context context{cache, TableComputation::kDynamic, nullptr,
-                  std::move(generator)};
-  SqliteTable::Register<DbSqliteTable, Context>(
-      db, std::move(context), table_name, false, requires_args);
-}
-
 base::Status DbSqliteTable::Init(int, const char* const*, Schema* schema) {
   switch (computation_) {
     case TableComputation::kStatic:
@@ -232,9 +209,9 @@
   info->sqlite_omit_order_by = true;
 }
 
-int DbSqliteTable::ModifyConstraints(QueryConstraints* qc) {
+base::Status DbSqliteTable::ModifyConstraints(QueryConstraints* qc) {
   ModifyConstraints(schema_, qc);
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
 void DbSqliteTable::ModifyConstraints(const Table::Schema& schema,
@@ -392,14 +369,15 @@
   return QueryCost{final_cost, current_row_count};
 }
 
-std::unique_ptr<SqliteTable::Cursor> DbSqliteTable::CreateCursor() {
+std::unique_ptr<SqliteTable::BaseCursor> DbSqliteTable::CreateCursor() {
   return std::unique_ptr<Cursor>(new Cursor(this, cache_));
 }
 
 DbSqliteTable::Cursor::Cursor(DbSqliteTable* sqlite_table, QueryCache* cache)
-    : SqliteTable::Cursor(sqlite_table),
+    : SqliteTable::BaseCursor(sqlite_table),
       db_sqlite_table_(sqlite_table),
       cache_(cache) {}
+DbSqliteTable::Cursor::~Cursor() = default;
 
 void DbSqliteTable::Cursor::TryCacheCreateSortedTable(
     const QueryConstraints& qc,
@@ -453,9 +431,9 @@
       });
 }
 
-int DbSqliteTable::Cursor::Filter(const QueryConstraints& qc,
-                                  sqlite3_value** argv,
-                                  FilterHistory history) {
+base::Status DbSqliteTable::Cursor::Filter(const QueryConstraints& qc,
+                                           sqlite3_value** argv,
+                                           FilterHistory history) {
   // Clear out the iterator before filtering to ensure the destructor is run
   // before the table's destructor.
   iterator_ = std::nullopt;
@@ -511,10 +489,8 @@
           constraints_, orders_, cols_used_bv, computed_table);
 
       if (!status.ok()) {
-        auto* sqlite_err = sqlite3_mprintf(
-            "%s: %s", db_sqlite_table_->name().c_str(), status.c_message());
-        db_sqlite_table_->SetErrorMessage(sqlite_err);
-        return SQLITE_CONSTRAINT;
+        return base::ErrStatus("%s: %s", db_sqlite_table_->name().c_str(),
+                               status.c_message());
       }
       PERFETTO_DCHECK(computed_table);
       dynamic_table_ = std::move(computed_table);
@@ -631,25 +607,24 @@
 
     eof_ = !*iterator_;
   }
-
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int DbSqliteTable::Cursor::Next() {
+base::Status DbSqliteTable::Cursor::Next() {
   if (mode_ == Mode::kSingleRow) {
     eof_ = true;
   } else {
     iterator_->Next();
     eof_ = !*iterator_;
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int DbSqliteTable::Cursor::Eof() {
+bool DbSqliteTable::Cursor::Eof() {
   return eof_;
 }
 
-int DbSqliteTable::Cursor::Column(sqlite3_context* ctx, int raw_col) {
+base::Status DbSqliteTable::Cursor::Column(sqlite3_context* ctx, int raw_col) {
   uint32_t column = static_cast<uint32_t>(raw_col);
   SqlValue value = mode_ == Mode::kSingleRow
                        ? SourceTable()->GetColumn(column).Get(*single_row_)
@@ -663,7 +638,7 @@
   // SQLite no longer cares about the bytes pointer.
   sqlite_utils::ReportSqlValue(ctx, value, sqlite_utils::kSqliteStatic,
                                sqlite_utils::kSqliteStatic);
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 6a2282d..96a63e5 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -17,6 +17,7 @@
 #ifndef SRC_TRACE_PROCESSOR_SQLITE_DB_SQLITE_TABLE_H_
 #define SRC_TRACE_PROCESSOR_SQLITE_DB_SQLITE_TABLE_H_
 
+#include "perfetto/base/status.h"
 #include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/db/table.h"
 #include "src/trace_processor/prelude/table_functions/table_function.h"
@@ -26,32 +27,48 @@
 namespace perfetto {
 namespace trace_processor {
 
+enum class DbSqliteTableComputation {
+  // Mode when the table is static (i.e. passed in at construction
+  // time).
+  kStatic,
+
+  // Mode when table is dynamically computed at filter time.
+  kDynamic,
+};
+
+struct DbSqliteTableContext {
+  QueryCache* cache;
+  DbSqliteTableComputation computation;
+
+  // Only valid when computation == TableComputation::kStatic.
+  const Table* static_table;
+
+  // Only valid when computation == TableComputation::kDynamic.
+  std::unique_ptr<TableFunction> generator;
+};
+
 // Implements the SQLite table interface for db tables.
-class DbSqliteTable : public SqliteTable {
+class DbSqliteTable final
+    : public TypedSqliteTable<DbSqliteTable, DbSqliteTableContext> {
  public:
-  enum class TableComputation {
-    // Mode when the table is static (i.e. passed in at construction
-    // time).
-    kStatic,
+  using TableComputation = DbSqliteTableComputation;
+  using Context = DbSqliteTableContext;
 
-    // Mode when table is dynamically computed at filter time.
-    kDynamic,
-  };
-
-  class Cursor : public SqliteTable::Cursor {
+  class Cursor final : public SqliteTable::BaseCursor {
    public:
     Cursor(DbSqliteTable*, QueryCache*);
+    ~Cursor() final;
 
     Cursor(Cursor&&) noexcept = default;
     Cursor& operator=(Cursor&&) = default;
 
     // Implementation of SqliteTable::Cursor.
-    int Filter(const QueryConstraints& qc,
-               sqlite3_value** argv,
-               FilterHistory) override;
-    int Next() override;
-    int Eof() override;
-    int Column(sqlite3_context*, int N) override;
+    base::Status Filter(const QueryConstraints& qc,
+                        sqlite3_value** argv,
+                        FilterHistory);
+    base::Status Next();
+    bool Eof();
+    base::Status Column(sqlite3_context*, int N);
 
    private:
     enum class Mode {
@@ -108,36 +125,15 @@
     double cost;
     uint32_t rows;
   };
-  struct Context {
-    QueryCache* cache;
-    TableComputation computation;
-
-    // Only valid when computation == TableComputation::kStatic.
-    const Table* static_table;
-
-    // Only valid when computation == TableComputation::kDynamic.
-    std::unique_ptr<TableFunction> generator;
-  };
-
-  static void RegisterTable(sqlite3* db,
-                            QueryCache* cache,
-                            const Table* table,
-                            const std::string& name);
-
-  static void RegisterTable(sqlite3* db,
-                            QueryCache* cache,
-                            std::unique_ptr<TableFunction> generator);
 
   DbSqliteTable(sqlite3*, Context context);
-  virtual ~DbSqliteTable() override;
+  virtual ~DbSqliteTable() final;
 
   // Table implementation.
-  base::Status Init(int,
-                    const char* const*,
-                    SqliteTable::Schema*) override final;
-  std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
-  int ModifyConstraints(QueryConstraints*) override final;
-  int BestIndex(const QueryConstraints&, BestIndexInfo*) override final;
+  base::Status Init(int, const char* const*, SqliteTable::Schema*) final;
+  std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
+  base::Status ModifyConstraints(QueryConstraints*) final;
+  int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
 
   // These static functions are useful to allow other callers to make use
   // of them.
diff --git a/src/trace_processor/sqlite/query_cache.h b/src/trace_processor/sqlite/query_cache.h
index f3b8223..1bb9d69 100644
--- a/src/trace_processor/sqlite/query_cache.h
+++ b/src/trace_processor/sqlite/query_cache.h
@@ -16,6 +16,7 @@
 
 #ifndef SRC_TRACE_PROCESSOR_SQLITE_QUERY_CACHE_H_
 #define SRC_TRACE_PROCESSOR_SQLITE_QUERY_CACHE_H_
+
 #include <optional>
 
 #include "src/trace_processor/db/table.h"
diff --git a/src/trace_processor/sqlite/sql_stats_table.cc b/src/trace_processor/sqlite/sql_stats_table.cc
index aafc93f..e6cdf87 100644
--- a/src/trace_processor/sqlite/sql_stats_table.cc
+++ b/src/trace_processor/sqlite/sql_stats_table.cc
@@ -22,6 +22,7 @@
 #include <bitset>
 #include <numeric>
 
+#include "perfetto/base/status.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
@@ -30,10 +31,7 @@
 
 SqlStatsTable::SqlStatsTable(sqlite3*, const TraceStorage* storage)
     : storage_(storage) {}
-
-void SqlStatsTable::RegisterTable(sqlite3* db, const TraceStorage* storage) {
-  SqliteTable::Register<SqlStatsTable>(db, storage, "sqlstats");
-}
+SqlStatsTable::~SqlStatsTable() = default;
 
 base::Status SqlStatsTable::Init(int, const char* const*, Schema* schema) {
   *schema = Schema(
@@ -50,8 +48,8 @@
   return util::OkStatus();
 }
 
-std::unique_ptr<SqliteTable::Cursor> SqlStatsTable::CreateCursor() {
-  return std::unique_ptr<SqliteTable::Cursor>(new Cursor(this));
+std::unique_ptr<SqliteTable::BaseCursor> SqlStatsTable::CreateCursor() {
+  return std::unique_ptr<SqliteTable::BaseCursor>(new Cursor(this));
 }
 
 int SqlStatsTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
@@ -59,28 +57,29 @@
 }
 
 SqlStatsTable::Cursor::Cursor(SqlStatsTable* table)
-    : SqliteTable::Cursor(table), storage_(table->storage_), table_(table) {}
-
+    : SqliteTable::BaseCursor(table),
+      storage_(table->storage_),
+      table_(table) {}
 SqlStatsTable::Cursor::~Cursor() = default;
 
-int SqlStatsTable::Cursor::Filter(const QueryConstraints&,
-                                  sqlite3_value**,
-                                  FilterHistory) {
+base::Status SqlStatsTable::Cursor::Filter(const QueryConstraints&,
+                                           sqlite3_value**,
+                                           FilterHistory) {
   *this = Cursor(table_);
   num_rows_ = storage_->sql_stats().size();
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int SqlStatsTable::Cursor::Next() {
+base::Status SqlStatsTable::Cursor::Next() {
   row_++;
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int SqlStatsTable::Cursor::Eof() {
+bool SqlStatsTable::Cursor::Eof() {
   return row_ >= num_rows_;
 }
 
-int SqlStatsTable::Cursor::Column(sqlite3_context* context, int col) {
+base::Status SqlStatsTable::Cursor::Column(sqlite3_context* context, int col) {
   const TraceStorage::SqlStats& stats = storage_->sql_stats();
   switch (col) {
     case Column::kQuery:
@@ -97,7 +96,7 @@
       sqlite3_result_int64(context, stats.times_ended()[row_]);
       break;
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/sqlite/sql_stats_table.h b/src/trace_processor/sqlite/sql_stats_table.h
index df8d9f5..d78224c 100644
--- a/src/trace_processor/sqlite/sql_stats_table.h
+++ b/src/trace_processor/sqlite/sql_stats_table.h
@@ -20,6 +20,7 @@
 #include <limits>
 #include <memory>
 
+#include "perfetto/base/status.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 
 namespace perfetto {
@@ -30,7 +31,8 @@
 
 // A virtual table that allows to introspect performances of the SQL engine
 // for the kMaxLogEntries queries.
-class SqlStatsTable : public SqliteTable {
+class SqlStatsTable final
+    : public TypedSqliteTable<SqlStatsTable, const TraceStorage*> {
  public:
   enum Column {
     kQuery = 0,
@@ -40,18 +42,18 @@
   };
 
   // Implementation of the SQLite cursor interface.
-  class Cursor : public SqliteTable::Cursor {
+  class Cursor final : public SqliteTable::BaseCursor {
    public:
-    Cursor(SqlStatsTable* storage);
-    ~Cursor() override;
+    explicit Cursor(SqlStatsTable* storage);
+    ~Cursor() final;
 
     // Implementation of SqliteTable::Cursor.
-    int Filter(const QueryConstraints&,
-               sqlite3_value**,
-               FilterHistory) override;
-    int Next() override;
-    int Eof() override;
-    int Column(sqlite3_context*, int N) override;
+    base::Status Filter(const QueryConstraints&,
+                        sqlite3_value**,
+                        FilterHistory);
+    base::Status Next();
+    bool Eof();
+    base::Status Column(sqlite3_context*, int N);
 
    private:
     Cursor(Cursor&) = delete;
@@ -67,13 +69,12 @@
   };
 
   SqlStatsTable(sqlite3*, const TraceStorage* storage);
-
-  static void RegisterTable(sqlite3* db, const TraceStorage* storage);
+  ~SqlStatsTable() final;
 
   // Table implementation.
-  base::Status Init(int, const char* const*, Schema*) override;
-  std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
-  int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
+  base::Status Init(int, const char* const*, Schema*) final;
+  std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
+  int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
 
  private:
   const TraceStorage* const storage_;
diff --git a/src/trace_processor/sqlite/sqlite_engine.cc b/src/trace_processor/sqlite/sqlite_engine.cc
new file mode 100644
index 0000000..7ffc3ed
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+#include "src/trace_processor/sqlite/sqlite_engine.h"
+
+#include <utility>
+
+#include "perfetto/base/status.h"
+#include "src/trace_processor/sqlite/db_sqlite_table.h"
+#include "src/trace_processor/sqlite/query_cache.h"
+#include "src/trace_processor/sqlite/sqlite_table.h"
+
+// In Android and Chromium tree builds, we don't have the percentile module.
+// Just don't include it.
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
+// defined in sqlite_src/ext/misc/percentile.c
+extern "C" int sqlite3_percentile_init(sqlite3* db,
+                                       char** error,
+                                       const sqlite3_api_routines* api);
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+void EnsureSqliteInitialized() {
+  // sqlite3_initialize isn't actually thread-safe despite being documented
+  // as such; we need to make sure multiple TraceProcessorImpl instances don't
+  // call it concurrently and only gets called once per process, instead.
+  static bool init_once = [] { return sqlite3_initialize() == SQLITE_OK; }();
+  PERFETTO_CHECK(init_once);
+}
+
+void InitializeSqlite(sqlite3* db) {
+  char* error = nullptr;
+  sqlite3_exec(db, "PRAGMA temp_store=2", nullptr, nullptr, &error);
+  if (error) {
+    PERFETTO_FATAL("Error setting pragma temp_store: %s", error);
+  }
+// In Android tree builds, we don't have the percentile module.
+// Just don't include it.
+#if PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
+  sqlite3_percentile_init(db, &error, nullptr);
+  if (error) {
+    PERFETTO_ELOG("Error initializing: %s", error);
+    sqlite3_free(error);
+  }
+#endif
+}
+
+}  // namespace
+
+SqliteEngine::SqliteEngine() : query_cache_(new QueryCache()) {
+  sqlite3* db = nullptr;
+  EnsureSqliteInitialized();
+  PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
+  InitializeSqlite(db);
+  db_.reset(std::move(db));
+}
+
+SqliteEngine::~SqliteEngine() {
+  // It is important to unregister any functions that have been registered with
+  // the database before destroying it. This is because functions can hold onto
+  // prepared statements, which must be finalized before database destruction.
+  for (auto it = fn_ctx_.GetIterator(); it; ++it) {
+    int ret = sqlite3_create_function_v2(db_.get(), it.key().first.c_str(),
+                                         it.key().second, SQLITE_UTF8, nullptr,
+                                         nullptr, nullptr, nullptr, nullptr);
+    PERFETTO_CHECK(ret == 0);
+  }
+  fn_ctx_.Clear();
+}
+
+void SqliteEngine::RegisterTable(const Table& table,
+                                 const std::string& table_name) {
+  DbSqliteTable::Context context{query_cache_.get(),
+                                 DbSqliteTable::TableComputation::kStatic,
+                                 &table, nullptr};
+  RegisterVirtualTableModule<DbSqliteTable>(table_name, std::move(context),
+                                            SqliteTable::kEponymousOnly, false);
+
+  // Register virtual tables into an internal 'perfetto_tables' table.
+  // This is used for iterating through all the tables during a database
+  // export.
+  char* insert_sql = sqlite3_mprintf(
+      "INSERT INTO perfetto_tables(name) VALUES('%q')", table_name.c_str());
+  char* error = nullptr;
+  sqlite3_exec(db_.get(), insert_sql, nullptr, nullptr, &error);
+  sqlite3_free(insert_sql);
+  if (error) {
+    PERFETTO_ELOG("Error adding table to perfetto_tables: %s", error);
+    sqlite3_free(error);
+  }
+}
+
+void SqliteEngine::RegisterTableFunction(std::unique_ptr<TableFunction> fn) {
+  std::string table_name = fn->TableName();
+  DbSqliteTable::Context context{query_cache_.get(),
+                                 DbSqliteTable::TableComputation::kDynamic,
+                                 nullptr, std::move(fn)};
+  RegisterVirtualTableModule<DbSqliteTable>(table_name, std::move(context),
+                                            SqliteTable::kEponymousOnly, false);
+}
+
+base::Status SqliteEngine::DeclareVirtualTable(const std::string& create_stmt) {
+  int res = sqlite3_declare_vtab(db_.get(), create_stmt.c_str());
+  if (res != SQLITE_OK) {
+    return base::ErrStatus("Declare vtab failed: %s",
+                           sqlite3_errmsg(db_.get()));
+  }
+  return base::OkStatus();
+}
+
+base::Status SqliteEngine::SaveSqliteTable(const std::string& table_name,
+                                           std::unique_ptr<SqliteTable> table) {
+  auto res = saved_tables_.Insert(table_name, {});
+  if (!res.second) {
+    return base::ErrStatus("Table with name %s already is saved",
+                           table_name.c_str());
+  }
+  *res.first = std::move(table);
+  return base::OkStatus();
+}
+
+base::StatusOr<std::unique_ptr<SqliteTable>> SqliteEngine::RestoreSqliteTable(
+    const std::string& table_name) {
+  auto* res = saved_tables_.Find(table_name);
+  if (!res) {
+    return base::ErrStatus("Table with name %s does not exist in saved state",
+                           table_name.c_str());
+  }
+  return std::move(*res);
+}
+
+void* SqliteEngine::GetFunctionContext(const std::string& name, int argc) {
+  auto* res = fn_ctx_.Find(std::make_pair(name, argc));
+  return res ? *res : nullptr;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_engine.h b/src/trace_processor/sqlite/sqlite_engine.h
new file mode 100644
index 0000000..2af8b81
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_engine.h
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACE_PROCESSOR_SQLITE_SQLITE_ENGINE_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_ENGINE_H_
+
+#include <sqlite3.h>
+#include <stdint.h>
+#include <functional>
+#include <memory>
+#include <type_traits>
+
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/hash.h"
+#include "src/trace_processor/db/table.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
+#include "src/trace_processor/prelude/table_functions/table_function.h"
+#include "src/trace_processor/sqlite/query_cache.h"
+#include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/sqlite_table.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Wrapper class around SQLite C API.
+//
+// The goal of this class is to provide a one-stop-shop mechanism to use SQLite.
+// Benefits of this include:
+// 1) It allows us to add code which intercepts registration of functions
+//    and tables and keeps track of this for later lookup.
+// 2) Allows easily auditing the SQLite APIs we use making it easy to determine
+//    what functionality we rely on.
+class SqliteEngine {
+ public:
+  SqliteEngine();
+  ~SqliteEngine();
+
+  // Registers a trace processor C++ table with SQLite with an SQL name of
+  // |name|.
+  void RegisterTable(const Table& table, const std::string& name);
+
+  // Registers a trace processor C++ function to be runnable from SQL.
+  //
+  // The format of the function is given by the |SqlFunction|.
+  //
+  // |db|:          sqlite3 database object
+  // |name|:        name of the function in SQL
+  // |argc|:        number of arguments for this function. This can be -1 if
+  //                the number of arguments is variable.
+  // |ctx|:         context object for the function (see SqlFunction::Run
+  // above);
+  //                this object *must* outlive the function so should likely be
+  //                either static or scoped to the lifetime of TraceProcessor.
+  // |determistic|: whether this function has deterministic output given the
+  //                same set of arguments.
+  template <typename Function = SqlFunction>
+  base::Status RegisterSqlFunction(const char* name,
+                                   int argc,
+                                   typename Function::Context* ctx,
+                                   bool deterministic = true);
+
+  // Registers a trace processor C++ function to be runnable from SQL.
+  //
+  // This function is the same as the above except allows a unique_ptr to be
+  // passed for the context; this allows for SQLite to manage the lifetime of
+  // this pointer instead of the essentially static requirement of the context
+  // pointer above.
+  template <typename Function>
+  base::Status RegisterSqlFunction(
+      const char* name,
+      int argc,
+      std::unique_ptr<typename Function::Context> ctx,
+      bool deterministic = true);
+
+  // Registers a trace processor C++ table function with SQLite.
+  void RegisterTableFunction(std::unique_ptr<TableFunction> fn);
+
+  // Registers a SQLite virtual table module with the given name.
+  //
+  // This API only exists for internal/legacy use: most callers should use
+  // one of the RegisterTable* APIs above.
+  template <typename Vtab, typename Context>
+  void RegisterVirtualTableModule(const std::string& module_name,
+                                  Context ctx,
+                                  SqliteTable::TableType table_type,
+                                  bool updatable);
+
+  // Declares a virtual table with SQLite.
+  //
+  // This API only exists for internal use. Most callers should never call this
+  // directly: instead use one of the RegisterTable* APIs above.
+  base::Status DeclareVirtualTable(const std::string& create_stmt);
+
+  // Saves a SQLite table across a pair of xDisconnect/xConnect callbacks.
+  //
+  // This API only exists for internal use. Most callers should never call this
+  // directly.
+  base::Status SaveSqliteTable(const std::string& table_name,
+                               std::unique_ptr<SqliteTable>);
+
+  // Restores a SQLite table across a pair of xDisconnect/xConnect callbacks.
+  //
+  // This API only exists for internal use. Most callers should never call this
+  // directly.
+  base::StatusOr<std::unique_ptr<SqliteTable>> RestoreSqliteTable(
+      const std::string& table_name);
+
+  // Gets the context for a registered SQL function.
+  //
+  // This API only exists for internal use. Most callers should never call this
+  // directly.
+  void* GetFunctionContext(const std::string& name, int argc);
+
+  sqlite3* db() const { return db_.get(); }
+
+ private:
+  struct FnHasher {
+    size_t operator()(const std::pair<std::string, int>& x) const {
+      base::Hasher hasher;
+      hasher.Update(x.first);
+      hasher.Update(x.second);
+      return static_cast<size_t>(hasher.digest());
+    }
+  };
+
+  std::unique_ptr<QueryCache> query_cache_;
+  base::FlatHashMap<std::string, std::unique_ptr<SqliteTable>> saved_tables_;
+  base::FlatHashMap<std::pair<std::string, int>, void*, FnHasher> fn_ctx_;
+
+  ScopedDb db_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+// The rest of this file is just implementation details which we need
+// in the header file because it is templated code. We separate it out
+// like this to keep the API people actually care about easy to read.
+
+namespace perfetto {
+namespace trace_processor {
+namespace sqlite_internal {
+
+// RAII type to call Function::Cleanup when destroyed.
+template <typename Function>
+struct ScopedCleanup {
+  typename Function::Context* ctx;
+  ~ScopedCleanup() { Function::Cleanup(ctx); }
+};
+
+template <typename Function>
+void WrapSqlFunction(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
+  using Context = typename Function::Context;
+  Context* ud = static_cast<Context*>(sqlite3_user_data(ctx));
+
+  ScopedCleanup<Function> scoped_cleanup{ud};
+  SqlValue value{};
+  SqlFunction::Destructors destructors{};
+  base::Status status =
+      Function::Run(ud, static_cast<size_t>(argc), argv, value, destructors);
+  if (!status.ok()) {
+    sqlite3_result_error(ctx, status.c_message(), -1);
+    return;
+  }
+
+  if (Function::kVoidReturn) {
+    if (!value.is_null()) {
+      sqlite3_result_error(ctx, "void SQL function returned value", -1);
+      return;
+    }
+
+    // If the function doesn't want to return anything, set the "VOID"
+    // pointer type to a non-null value. Note that because of the weird
+    // way |sqlite3_value_pointer| works, we need to set some value even
+    // if we don't actually read it - just set it to a pointer to an empty
+    // string for this reason.
+    static char kVoidValue[] = "";
+    sqlite3_result_pointer(ctx, kVoidValue, "VOID", nullptr);
+  } else {
+    sqlite_utils::ReportSqlValue(ctx, value, destructors.string_destructor,
+                                 destructors.bytes_destructor);
+  }
+
+  status = Function::VerifyPostConditions(ud);
+  if (!status.ok()) {
+    sqlite3_result_error(ctx, status.c_message(), -1);
+    return;
+  }
+}
+
+}  // namespace sqlite_internal
+
+template <typename Function>
+base::Status SqliteEngine::RegisterSqlFunction(const char* name,
+                                               int argc,
+                                               typename Function::Context* ctx,
+                                               bool deterministic) {
+  int flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0);
+  int ret = sqlite3_create_function_v2(
+      db_.get(), name, static_cast<int>(argc), flags, ctx,
+      sqlite_internal::WrapSqlFunction<Function>, nullptr, nullptr, nullptr);
+  if (ret != SQLITE_OK) {
+    return base::ErrStatus("Unable to register function with name %s", name);
+  }
+  *fn_ctx_.Insert(std::make_pair(name, argc), ctx).first = ctx;
+  return base::OkStatus();
+}
+
+template <typename Function>
+base::Status SqliteEngine::RegisterSqlFunction(
+    const char* name,
+    int argc,
+    std::unique_ptr<typename Function::Context> user_data,
+    bool deterministic) {
+  int flags = SQLITE_UTF8 | (deterministic ? SQLITE_DETERMINISTIC : 0);
+  void* fn_ctx = user_data.get();
+  int ret = sqlite3_create_function_v2(
+      db_.get(), name, static_cast<int>(argc), flags, user_data.release(),
+      sqlite_internal::WrapSqlFunction<Function>, nullptr, nullptr,
+      [](void* ptr) { delete static_cast<typename Function::Context*>(ptr); });
+  if (ret != SQLITE_OK) {
+    return base::ErrStatus("Unable to register function with name %s", name);
+  }
+  *fn_ctx_.Insert(std::make_pair(name, argc), fn_ctx).first = fn_ctx;
+  return base::OkStatus();
+}
+
+template <typename Vtab, typename Context>
+void SqliteEngine::RegisterVirtualTableModule(const std::string& module_name,
+                                              Context ctx,
+                                              SqliteTable::TableType table_type,
+                                              bool updatable) {
+  static_assert(std::is_base_of_v<SqliteTable, Vtab>,
+                "Must subclass TypedSqliteTable");
+
+  auto module_arg =
+      Vtab::CreateModuleArg(this, std::move(ctx), table_type, updatable);
+  sqlite3_module* module = &module_arg->module;
+  int res = sqlite3_create_module_v2(
+      db_.get(), module_name.c_str(), module, module_arg.release(),
+      [](void* arg) { delete static_cast<typename Vtab::ModuleArg*>(arg); });
+  PERFETTO_CHECK(res == SQLITE_OK);
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_ENGINE_H_
diff --git a/src/trace_processor/sqlite/sqlite_table.cc b/src/trace_processor/sqlite/sqlite_table.cc
index a0c5ce9..2ad925c 100644
--- a/src/trace_processor/sqlite/sqlite_table.cc
+++ b/src/trace_processor/sqlite/sqlite_table.cc
@@ -20,9 +20,16 @@
 #include <algorithm>
 #include <cinttypes>
 #include <map>
+#include <memory>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/status_or.h"
+#include "perfetto/ext/base/string_view.h"
+#include "sqlite3.h"
+#include "src/trace_processor/sqlite/sqlite_engine.h"
 #include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/util/status_macros.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -153,98 +160,16 @@
 SqliteTable::SqliteTable() = default;
 SqliteTable::~SqliteTable() = default;
 
-int SqliteTable::OpenInternal(sqlite3_vtab_cursor** ppCursor) {
-  // Freed in xClose().
-  *ppCursor = static_cast<sqlite3_vtab_cursor*>(CreateCursor().release());
-  return SQLITE_OK;
-}
-
-int SqliteTable::BestIndexInternal(sqlite3_index_info* idx) {
-  QueryConstraints qc(idx->colUsed);
-
-  for (int i = 0; i < idx->nConstraint; i++) {
-    const auto& cs = idx->aConstraint[i];
-    if (!cs.usable)
-      continue;
-    qc.AddConstraint(cs.iColumn, cs.op, i);
-  }
-
-  for (int i = 0; i < idx->nOrderBy; i++) {
-    int column = idx->aOrderBy[i].iColumn;
-    bool desc = idx->aOrderBy[i].desc;
-    qc.AddOrderBy(column, desc);
-  }
-
-  int ret = ModifyConstraints(&qc);
-  if (ret != SQLITE_OK)
-    return ret;
-
-  BestIndexInfo info;
-  info.estimated_cost = idx->estimatedCost;
-  info.estimated_rows = idx->estimatedRows;
-  info.sqlite_omit_constraint.resize(qc.constraints().size());
-
-  ret = BestIndex(qc, &info);
-
-  if (ret != SQLITE_OK)
-    return ret;
-
-  idx->orderByConsumed = qc.order_by().empty() || info.sqlite_omit_order_by;
-  idx->estimatedCost = info.estimated_cost;
-  idx->estimatedRows = info.estimated_rows;
-
-  // First pass: mark all constraints as omitted to ensure that any pruned
-  // constraints are not checked for by SQLite.
-  for (int i = 0; i < idx->nConstraint; ++i) {
-    auto& u = idx->aConstraintUsage[i];
-    u.omit = true;
-  }
-
-  // Second pass: actually set the correct omit and index values for all
-  // retained constraints.
-  for (uint32_t i = 0; i < qc.constraints().size(); ++i) {
-    auto& u = idx->aConstraintUsage[qc.constraints()[i].a_constraint_idx];
-    u.omit = info.sqlite_omit_constraint[i];
-    u.argvIndex = static_cast<int>(i) + 1;
-  }
-
-  PERFETTO_TP_TRACE(
-      metatrace::Category::QUERY, "SQLITE_TABLE_BEST_INDEX",
-      [&](metatrace::Record* r) {
-        r->AddArg("name", name_);
-        WriteQueryConstraintsToMetatrace(r, qc, schema());
-        r->AddArg("order_by_consumed", std::to_string(idx->orderByConsumed));
-        r->AddArg("estimated_cost", std::to_string(idx->estimatedCost));
-        r->AddArg("estimated_rows",
-                  std::to_string(static_cast<int64_t>(idx->estimatedRows)));
-      });
-
-  auto out_qc_str = qc.ToNewSqlite3String();
-  if (SqliteTable::debug) {
-    PERFETTO_LOG(
-        "[%s::BestIndex] constraints=%s orderByConsumed=%d estimatedCost=%f "
-        "estimatedRows=%" PRId64,
-        name_.c_str(), QcDebugStr(qc, schema()).c_str(), idx->orderByConsumed,
-        idx->estimatedCost, static_cast<int64_t>(idx->estimatedRows));
-  }
-
-  idx->idxStr = out_qc_str.release();
-  idx->needToFreeIdxStr = true;
-  idx->idxNum = ++best_index_num_;
-
-  return SQLITE_OK;
-}
-
-int SqliteTable::ModifyConstraints(QueryConstraints*) {
-  return SQLITE_OK;
+base::Status SqliteTable::ModifyConstraints(QueryConstraints*) {
+  return base::OkStatus();
 }
 
 int SqliteTable::FindFunction(const char*, FindFunctionFn*, void**) {
   return 0;
 }
 
-int SqliteTable::Update(int, sqlite3_value**, sqlite3_int64*) {
-  return SQLITE_READONLY;
+base::Status SqliteTable::Update(int, sqlite3_value**, sqlite3_int64*) {
+  return base::ErrStatus("Updating not supported");
 }
 
 bool SqliteTable::ReadConstraints(int idxNum, const char* idxStr, int argc) {
@@ -274,16 +199,20 @@
   return cache_hit;
 }
 
-SqliteTable::Cursor::Cursor(SqliteTable* table) : table_(table) {
+////////////////////////////////////////////////////////////////////////////////
+// SqliteTable::BaseCursor implementation
+////////////////////////////////////////////////////////////////////////////////
+
+SqliteTable::BaseCursor::BaseCursor(SqliteTable* table) : table_(table) {
   // This is required to prevent us from leaving this field uninitialised if
   // we ever move construct the Cursor.
   pVtab = table;
 }
-SqliteTable::Cursor::~Cursor() = default;
+SqliteTable::BaseCursor::~BaseCursor() = default;
 
-int SqliteTable::Cursor::RowId(sqlite3_int64*) {
-  return SQLITE_ERROR;
-}
+////////////////////////////////////////////////////////////////////////////////
+// SqliteTable::Column implementation
+////////////////////////////////////////////////////////////////////////////////
 
 SqliteTable::Column::Column(size_t index,
                             std::string name,
@@ -291,6 +220,12 @@
                             bool hidden)
     : index_(index), name_(name), type_(type), hidden_(hidden) {}
 
+////////////////////////////////////////////////////////////////////////////////
+// SqliteTable::Schema implementation
+////////////////////////////////////////////////////////////////////////////////
+
+SqliteTable::Schema::Schema() = default;
+
 SqliteTable::Schema::Schema(std::vector<Column> columns,
                             std::vector<size_t> primary_keys)
     : columns_(std::move(columns)), primary_keys_(std::move(primary_keys)) {
@@ -302,7 +237,6 @@
   }
 }
 
-SqliteTable::Schema::Schema() = default;
 SqliteTable::Schema::Schema(const Schema&) = default;
 SqliteTable::Schema& SqliteTable::Schema::operator=(const Schema&) = default;
 
@@ -334,5 +268,170 @@
   return stmt;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// TypedSqliteTableBase implementation
+////////////////////////////////////////////////////////////////////////////////
+
+TypedSqliteTableBase::~TypedSqliteTableBase() = default;
+
+base::Status TypedSqliteTableBase::DeclareAndAssignVtab(
+    std::unique_ptr<SqliteTable> table,
+    sqlite3_vtab** tab) {
+  auto create_stmt = table->schema().ToCreateTableStmt();
+  PERFETTO_DLOG("Create table statement: %s", create_stmt.c_str());
+  RETURN_IF_ERROR(table->engine_->DeclareVirtualTable(create_stmt));
+  *tab = table.release();
+  return base::OkStatus();
+}
+
+int TypedSqliteTableBase::xDestroy(sqlite3_vtab* t) {
+  delete static_cast<SqliteTable*>(t);
+  return SQLITE_OK;
+}
+
+int TypedSqliteTableBase::xDestroyFatal(sqlite3_vtab*) {
+  PERFETTO_FATAL("xDestroy should not be called");
+}
+
+int TypedSqliteTableBase::xConnectRestoreTable(sqlite3*,
+                                               void* arg,
+                                               int,
+                                               const char* const* argv,
+                                               sqlite3_vtab** tab,
+                                               char** pzErr) {
+  auto* xArg = static_cast<BaseModuleArg*>(arg);
+
+  // SQLite guarantees that argv[2] contains the name of the table.
+  std::string table_name = argv[2];
+  base::StatusOr<std::unique_ptr<SqliteTable>> table =
+      xArg->engine->RestoreSqliteTable(table_name);
+  if (!table.status().ok()) {
+    *pzErr = sqlite3_mprintf("%s", table.status().c_message());
+    return SQLITE_ERROR;
+  }
+  base::Status status = DeclareAndAssignVtab(std::move(table.value()), tab);
+  if (!status.ok()) {
+    *pzErr = sqlite3_mprintf("%s", status.c_message());
+    return SQLITE_ERROR;
+  }
+  return SQLITE_OK;
+}
+
+int TypedSqliteTableBase::xDisconnectSaveTable(sqlite3_vtab* t) {
+  auto* table = static_cast<TypedSqliteTableBase*>(t);
+  base::Status status = table->engine_->SaveSqliteTable(
+      table->name(), std::unique_ptr<SqliteTable>(table));
+  return table->SetStatusAndReturn(status);
+}
+
+base::Status TypedSqliteTableBase::InitInternal(SqliteEngine* engine,
+                                                int argc,
+                                                const char* const* argv) {
+  // Set the engine to allow saving into it later.
+  engine_ = engine;
+
+  // SQLite guarantees that argv[0] will be the "module" name: this is the
+  // same as |table_name| passed to the Register function.
+  module_name_ = argv[0];
+
+  // SQLite guarantees that argv[2] contains the name of the table: for
+  // non-arg taking tables, this will be the same as |table_name| but for
+  // arg-taking tables, this will be the table name as defined by the
+  // user in the CREATE VIRTUAL TABLE call.
+  name_ = argv[2];
+
+  Schema schema;
+  RETURN_IF_ERROR(Init(argc, argv, &schema));
+  schema_ = std::move(schema);
+  return base::OkStatus();
+}
+
+int TypedSqliteTableBase::xOpen(sqlite3_vtab* t,
+                                sqlite3_vtab_cursor** ppCursor) {
+  auto* table = static_cast<TypedSqliteTableBase*>(t);
+  *ppCursor =
+      static_cast<sqlite3_vtab_cursor*>(table->CreateCursor().release());
+  return SQLITE_OK;
+}
+
+int TypedSqliteTableBase::xBestIndex(sqlite3_vtab* t, sqlite3_index_info* idx) {
+  auto* table = static_cast<TypedSqliteTableBase*>(t);
+
+  QueryConstraints qc(idx->colUsed);
+
+  for (int i = 0; i < idx->nConstraint; i++) {
+    const auto& cs = idx->aConstraint[i];
+    if (!cs.usable)
+      continue;
+    qc.AddConstraint(cs.iColumn, cs.op, i);
+  }
+
+  for (int i = 0; i < idx->nOrderBy; i++) {
+    int column = idx->aOrderBy[i].iColumn;
+    bool desc = idx->aOrderBy[i].desc;
+    qc.AddOrderBy(column, desc);
+  }
+
+  int ret = table->SetStatusAndReturn(table->ModifyConstraints(&qc));
+  if (ret != SQLITE_OK)
+    return ret;
+
+  BestIndexInfo info;
+  info.estimated_cost = idx->estimatedCost;
+  info.estimated_rows = idx->estimatedRows;
+  info.sqlite_omit_constraint.resize(qc.constraints().size());
+
+  ret = table->BestIndex(qc, &info);
+
+  if (ret != SQLITE_OK)
+    return ret;
+
+  idx->orderByConsumed = qc.order_by().empty() || info.sqlite_omit_order_by;
+  idx->estimatedCost = info.estimated_cost;
+  idx->estimatedRows = info.estimated_rows;
+
+  // First pass: mark all constraints as omitted to ensure that any pruned
+  // constraints are not checked for by SQLite.
+  for (int i = 0; i < idx->nConstraint; ++i) {
+    auto& u = idx->aConstraintUsage[i];
+    u.omit = true;
+  }
+
+  // Second pass: actually set the correct omit and index values for all
+  // retained constraints.
+  for (uint32_t i = 0; i < qc.constraints().size(); ++i) {
+    auto& u = idx->aConstraintUsage[qc.constraints()[i].a_constraint_idx];
+    u.omit = info.sqlite_omit_constraint[i];
+    u.argvIndex = static_cast<int>(i) + 1;
+  }
+
+  PERFETTO_TP_TRACE(
+      metatrace::Category::QUERY, "SQLITE_TABLE_BEST_INDEX",
+      [&](metatrace::Record* r) {
+        r->AddArg("name", table->name());
+        WriteQueryConstraintsToMetatrace(r, qc, table->schema());
+        r->AddArg("order_by_consumed", std::to_string(idx->orderByConsumed));
+        r->AddArg("estimated_cost", std::to_string(idx->estimatedCost));
+        r->AddArg("estimated_rows",
+                  std::to_string(static_cast<int64_t>(idx->estimatedRows)));
+      });
+
+  auto out_qc_str = qc.ToNewSqlite3String();
+  if (SqliteTable::debug) {
+    PERFETTO_LOG(
+        "[%s::BestIndex] constraints=%s orderByConsumed=%d estimatedCost=%f "
+        "estimatedRows=%" PRId64,
+        table->name().c_str(), QcDebugStr(qc, table->schema()).c_str(),
+        idx->orderByConsumed, idx->estimatedCost,
+        static_cast<int64_t>(idx->estimatedRows));
+  }
+
+  idx->idxStr = out_qc_str.release();
+  idx->needToFreeIdxStr = true;
+  idx->idxNum = ++table->best_index_num_;
+
+  return SQLITE_OK;
+}
+
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_table.h b/src/trace_processor/sqlite/sqlite_table.h
index de94669..2d0cabc 100644
--- a/src/trace_processor/sqlite/sqlite_table.h
+++ b/src/trace_processor/sqlite/sqlite_table.h
@@ -27,31 +27,30 @@
 #include <vector>
 
 #include "perfetto/base/status.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+#include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/db/table.h"
 #include "src/trace_processor/sqlite/query_constraints.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-class TraceStorage;
+class SqliteEngine;
+class TypedSqliteTableBase;
 
 // Abstract base class representing a SQLite virtual table. Implements the
 // common bookeeping required across all tables and allows subclasses to
 // implement a friendlier API than that required by SQLite.
 class SqliteTable : public sqlite3_vtab {
  public:
-  template <typename Context>
-  using Factory =
-      std::function<std::unique_ptr<SqliteTable>(sqlite3*, Context)>;
-
   // Custom opcodes used by subclasses of SqliteTable.
   // Stored here as we need a central repository of opcodes to prevent clashes
   // between different sub-classes.
   enum CustomFilterOpcode {
     kSourceGeqOpCode = SQLITE_INDEX_CONSTRAINT_FUNCTION + 1,
   };
-
   // Describes a column of this table.
   class Column {
    public:
@@ -74,15 +73,9 @@
     bool hidden_ = false;
   };
 
-  // When set it logs all BestIndex and Filter actions on the console.
-  static bool debug;
-
-  // Public for unique_ptr destructor calls.
-  virtual ~SqliteTable();
-
   // Abstract base class representing an SQLite Cursor. Presents a friendlier
   // API for subclasses to implement.
-  class Cursor : public sqlite3_vtab_cursor {
+  class BaseCursor : public sqlite3_vtab_cursor {
    public:
     // Enum for the history of calls to Filter.
     enum class FilterHistory : uint32_t {
@@ -97,39 +90,39 @@
       kSame = 1,
     };
 
-    explicit Cursor(SqliteTable* table);
-    virtual ~Cursor();
+    explicit BaseCursor(SqliteTable* table);
+    virtual ~BaseCursor();
 
     // Methods to be implemented by derived table classes.
+    // Note: these methods are intentionally not virtual for performance
+    // reasons. As these methods are not defined, there will be compile errors
+    // thrown if any of these methods are missing.
 
     // Called to intialise the cursor with the constraints of the query.
-    virtual int Filter(const QueryConstraints& qc,
-                       sqlite3_value**,
-                       FilterHistory) = 0;
+    base::Status Filter(const QueryConstraints& qc,
+                        sqlite3_value**,
+                        FilterHistory);
 
     // Called to forward the cursor to the next row in the table.
-    virtual int Next() = 0;
+    base::Status Next();
 
     // Called to check if the cursor has reached eof. Column will be called iff
     // this method returns true.
-    virtual int Eof() = 0;
+    bool Eof();
 
     // Used to extract the value from the column at index |N|.
-    virtual int Column(sqlite3_context* context, int N) = 0;
+    base::Status Column(sqlite3_context* context, int N);
 
-    // Optional methods to implement.
-    virtual int RowId(sqlite3_int64*);
+    SqliteTable* table() const { return table_; }
 
    protected:
-    Cursor(Cursor&) = delete;
-    Cursor& operator=(const Cursor&) = delete;
+    BaseCursor(BaseCursor&) = delete;
+    BaseCursor& operator=(const BaseCursor&) = delete;
 
-    Cursor(Cursor&&) noexcept = default;
-    Cursor& operator=(Cursor&&) = default;
+    BaseCursor(BaseCursor&&) noexcept = default;
+    BaseCursor& operator=(BaseCursor&&) = default;
 
    private:
-    friend class SqliteTable;
-
     SqliteTable* table_ = nullptr;
   };
 
@@ -159,6 +152,24 @@
     std::vector<size_t> primary_keys_;
   };
 
+  enum TableType {
+    // A table which automatically exists in the main schema and cannot be
+    // created with CREATE VIRTUAL TABLE.
+    // Note: the name value here matches the naming in the vtable docs of
+    // SQLite.
+    kEponymousOnly,
+
+    // A table which must be explicitly created using a CREATE VIRTUAL TABLE
+    // statement (i.e. does exist automatically).
+    kExplicitCreate,
+  };
+
+  // Public for unique_ptr destructor calls.
+  virtual ~SqliteTable();
+
+  // When set it logs all BestIndex and Filter actions on the console.
+  static bool debug;
+
  protected:
   // Populated by a BestIndex call to allow subclasses to tweak SQLite's
   // handling of sets of constraints.
@@ -185,187 +196,39 @@
     int64_t estimated_rows = 0;
   };
 
-  template <typename Context>
-  struct TableDescriptor {
-    SqliteTable::Factory<Context> factory;
-    Context context;
-    sqlite3_module module = {};
-  };
-
   SqliteTable();
 
-  // Called by derived classes to register themselves with the SQLite db.
-  // |read_write| specifies whether the table can also be written to.
-  // |requires_args| should be true if the table requires arguments in order to
-  // be instantiated.
-  // Note: this function is inlined here because we use the TTable template to
-  // devirtualise the function calls.
-  template <typename TTable, typename Context = const TraceStorage*>
-  static void Register(sqlite3* db,
-                       Context ctx,
-                       const std::string& module_name,
-                       bool read_write = false,
-                       bool requires_args = false) {
-    using TCursor = typename TTable::Cursor;
-
-    std::unique_ptr<TableDescriptor<Context>> desc(
-        new TableDescriptor<Context>());
-    desc->context = std::move(ctx);
-    desc->factory = GetFactory<TTable, Context>();
-    sqlite3_module* module = &desc->module;
-    memset(module, 0, sizeof(*module));
-
-    auto create_fn = [](sqlite3* xdb, void* arg, int argc,
-                        const char* const* argv, sqlite3_vtab** tab,
-                        char** pzErr) {
-      auto* xdesc = static_cast<TableDescriptor<Context>*>(arg);
-      auto table = xdesc->factory(xdb, std::move(xdesc->context));
-
-      // SQLite guarantees that argv[0] will be the "module" name: this is the
-      // same as |table_name| passed to the Register function.
-      table->module_name_ = argv[0];
-
-      // SQLite guarantees that argv[2] contains the name of the table: for
-      // non-arg taking tables, this will be the same as |table_name| but for
-      // arg-taking tables, this will be the table name as defined by the user
-      // in the CREATE VIRTUAL TABLE call.
-      table->name_ = argv[2];
-
-      Schema schema;
-      base::Status status = table->Init(argc, argv, &schema);
-      if (!status.ok()) {
-        *pzErr = sqlite3_mprintf("%s", status.c_message());
-        return SQLITE_ERROR;
-      }
-
-      auto create_stmt = schema.ToCreateTableStmt();
-      PERFETTO_DLOG("Create table statement: %s", create_stmt.c_str());
-
-      int res = sqlite3_declare_vtab(xdb, create_stmt.c_str());
-      if (res != SQLITE_OK)
-        return res;
-
-      // Freed in xDisconnect().
-      table->schema_ = std::move(schema);
-      *tab = table.release();
-
-      return SQLITE_OK;
-    };
-    auto destroy_fn = [](sqlite3_vtab* t) {
-      delete static_cast<TTable*>(t);
-      return SQLITE_OK;
-    };
-
-    module->xCreate = create_fn;
-    module->xConnect = create_fn;
-    module->xDisconnect = destroy_fn;
-    module->xDestroy = destroy_fn;
-    module->xOpen = [](sqlite3_vtab* t, sqlite3_vtab_cursor** c) {
-      return static_cast<TTable*>(t)->OpenInternal(c);
-    };
-    module->xClose = [](sqlite3_vtab_cursor* c) {
-      delete static_cast<TCursor*>(c);
-      return SQLITE_OK;
-    };
-    module->xBestIndex = [](sqlite3_vtab* t, sqlite3_index_info* i) {
-      return static_cast<TTable*>(t)->BestIndexInternal(i);
-    };
-    module->xFilter = [](sqlite3_vtab_cursor* vc, int i, const char* s, int a,
-                         sqlite3_value** v) {
-      auto* c = static_cast<Cursor*>(vc);
-      bool is_cached = c->table_->ReadConstraints(i, s, a);
-
-      auto history = is_cached ? Cursor::FilterHistory::kSame
-                               : Cursor::FilterHistory::kDifferent;
-      return static_cast<TCursor*>(c)->Filter(c->table_->qc_cache_, v, history);
-    };
-    module->xNext = [](sqlite3_vtab_cursor* c) {
-      return static_cast<TCursor*>(c)->Next();
-    };
-    module->xEof = [](sqlite3_vtab_cursor* c) {
-      return static_cast<TCursor*>(c)->Eof();
-    };
-    module->xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
-      return static_cast<TCursor*>(c)->Column(a, b);
-    };
-    module->xRowid = [](sqlite3_vtab_cursor* c, sqlite3_int64* r) {
-      return static_cast<TCursor*>(c)->RowId(r);
-    };
-    module->xFindFunction =
-        [](sqlite3_vtab* t, int, const char* name,
-           void (**fn)(sqlite3_context*, int, sqlite3_value**), void** args) {
-          return static_cast<TTable*>(t)->FindFunction(name, fn, args);
-        };
-
-    if (read_write) {
-      module->xUpdate = [](sqlite3_vtab* t, int a, sqlite3_value** v,
-                           sqlite3_int64* r) {
-        return static_cast<TTable*>(t)->Update(a, v, r);
-      };
-    }
-
-    int res = sqlite3_create_module_v2(
-        db, module_name.c_str(), module, desc.release(),
-        [](void* arg) { delete static_cast<TableDescriptor<Context>*>(arg); });
-    PERFETTO_CHECK(res == SQLITE_OK);
-
-    // Register virtual tables into an internal 'perfetto_tables' table. This is
-    // used for iterating through all the tables during a database export. Note
-    // that virtual tables requiring arguments aren't registered because they
-    // can't be automatically instantiated for exporting.
-    if (!requires_args) {
-      char* insert_sql =
-          sqlite3_mprintf("INSERT INTO perfetto_tables(name) VALUES('%q')",
-                          module_name.c_str());
-      char* error = nullptr;
-      sqlite3_exec(db, insert_sql, nullptr, nullptr, &error);
-      sqlite3_free(insert_sql);
-      if (error) {
-        PERFETTO_ELOG("Error registering table: %s", error);
-        sqlite3_free(error);
-      }
-    }
-  }
-
   // Methods to be implemented by derived table classes.
   virtual base::Status Init(int argc, const char* const* argv, Schema*) = 0;
-  virtual std::unique_ptr<Cursor> CreateCursor() = 0;
+  virtual std::unique_ptr<BaseCursor> CreateCursor() = 0;
   virtual int BestIndex(const QueryConstraints& qc, BestIndexInfo* info) = 0;
 
   // Optional metods to implement.
   using FindFunctionFn = void (*)(sqlite3_context*, int, sqlite3_value**);
-  virtual int ModifyConstraints(QueryConstraints* qc);
+  virtual base::Status ModifyConstraints(QueryConstraints* qc);
   virtual int FindFunction(const char* name, FindFunctionFn* fn, void** args);
 
   // At registration time, the function should also pass true for |read_write|.
-  virtual int Update(int, sqlite3_value**, sqlite3_int64*);
+  virtual base::Status Update(int, sqlite3_value**, sqlite3_int64*);
 
-  void SetErrorMessage(char* error) {
-    sqlite3_free(zErrMsg);
-    zErrMsg = error;
-  }
+  bool ReadConstraints(int idxNum, const char* idxStr, int argc);
 
   const Schema& schema() const { return schema_; }
   const std::string& module_name() const { return module_name_; }
   const std::string& name() const { return name_; }
 
  private:
-  template <typename TableType, typename Context>
-  static Factory<Context> GetFactory() {
-    return [](sqlite3* db, Context ctx) {
-      return std::unique_ptr<SqliteTable>(new TableType(db, std::move(ctx)));
-    };
-  }
-
-  bool ReadConstraints(int idxNum, const char* idxStr, int argc);
-
-  // Overriden functions from sqlite3_vtab.
-  int OpenInternal(sqlite3_vtab_cursor**);
-  int BestIndexInternal(sqlite3_index_info*);
+  template <typename, typename>
+  friend class TypedSqliteTable;
+  friend class TypedSqliteTableBase;
 
   SqliteTable(const SqliteTable&) = delete;
   SqliteTable& operator=(const SqliteTable&) = delete;
 
+  // The engine class this table is registered with. Used for restoring/saving
+  // the table.
+  SqliteEngine* engine_ = nullptr;
+
   // This name of the table. For tables created using CREATE VIRTUAL TABLE, this
   // will be the name of the table specified by the query. For automatically
   // created tables, this will be the same as the module name passed to
@@ -384,6 +247,177 @@
   int best_index_num_ = 0;
 };
 
+class TypedSqliteTableBase : public SqliteTable {
+ protected:
+  struct BaseModuleArg {
+    sqlite3_module module;
+    SqliteEngine* engine;
+  };
+
+  ~TypedSqliteTableBase() override;
+
+  static int xDestroy(sqlite3_vtab*);
+  static int xDestroyFatal(sqlite3_vtab*);
+
+  static int xConnectRestoreTable(sqlite3* xdb,
+                                  void* arg,
+                                  int argc,
+                                  const char* const* argv,
+                                  sqlite3_vtab** tab,
+                                  char** pzErr);
+  static int xDisconnectSaveTable(sqlite3_vtab*);
+
+  static int xOpen(sqlite3_vtab*, sqlite3_vtab_cursor**);
+  static int xBestIndex(sqlite3_vtab*, sqlite3_index_info*);
+
+  static base::Status DeclareAndAssignVtab(std::unique_ptr<SqliteTable> table,
+                                           sqlite3_vtab** tab);
+
+  base::Status InitInternal(SqliteEngine* engine,
+                            int argc,
+                            const char* const* argv);
+
+  int SetStatusAndReturn(base::Status status) {
+    if (!status.ok()) {
+      sqlite3_free(zErrMsg);
+      zErrMsg = sqlite3_mprintf("%s", status.c_message());
+      return SQLITE_ERROR;
+    }
+    return SQLITE_OK;
+  }
+};
+
+template <typename SubTable, typename Context>
+class TypedSqliteTable : public TypedSqliteTableBase {
+ public:
+  struct ModuleArg : BaseModuleArg {
+    Context context;
+  };
+
+  static std::unique_ptr<ModuleArg> CreateModuleArg(SqliteEngine* engine,
+                                                    Context ctx,
+                                                    TableType table_type,
+                                                    bool updatable) {
+    auto arg = std::make_unique<ModuleArg>();
+    arg->module = CreateModule(table_type, updatable);
+    arg->engine = engine;
+    arg->context = std::move(ctx);
+    return arg;
+  }
+
+ private:
+  static constexpr sqlite3_module CreateModule(TableType table_type,
+                                               bool updatable) {
+    sqlite3_module module;
+    memset(&module, 0, sizeof(sqlite3_module));
+    switch (table_type) {
+      case TableType::kEponymousOnly:
+        // Neither xCreate nor xDestroy should ever be called for
+        // eponymous-only tables.
+        module.xCreate = nullptr;
+        module.xDestroy = &xDestroyFatal;
+
+        // xConnect and xDisconnect will automatically be called with
+        // |module_name| == |name|.
+        module.xConnect = &xCreate;
+        module.xDisconnect = &xDestroy;
+        break;
+      case TableType::kExplicitCreate:
+        // xConnect and xDestroy will be called when the table is CREATE-ed and
+        // DROP-ed respectively.
+        module.xCreate = &xCreate;
+        module.xDestroy = &xDestroy;
+
+        // xConnect and xDisconnect can be called at any time.
+        module.xConnect = &xConnectRestoreTable;
+        module.xDisconnect = &xDisconnectSaveTable;
+        break;
+    }
+    module.xOpen = &xOpen;
+    module.xClose = &xClose;
+    module.xBestIndex = &xBestIndex;
+    module.xFindFunction = &xFindFunction;
+    module.xFilter = &xFilter;
+    module.xNext = &xNext;
+    module.xEof = &xEof;
+    module.xColumn = &xColumn;
+    module.xRowid = &xRowid;
+    if (updatable) {
+      module.xUpdate = &xUpdate;
+    }
+    return module;
+  }
+
+  static int xCreate(sqlite3* xdb,
+                     void* arg,
+                     int argc,
+                     const char* const* argv,
+                     sqlite3_vtab** tab,
+                     char** pzErr) {
+    auto* xdesc = static_cast<ModuleArg*>(arg);
+    std::unique_ptr<SubTable> table(
+        new SubTable(xdb, std::move(xdesc->context)));
+    base::Status status = table->InitInternal(xdesc->engine, argc, argv);
+    if (!status.ok()) {
+      *pzErr = sqlite3_mprintf("%s", status.c_message());
+      return SQLITE_ERROR;
+    }
+    status = DeclareAndAssignVtab(std::move(table), tab);
+    if (!status.ok()) {
+      *pzErr = sqlite3_mprintf("%s", status.c_message());
+      return SQLITE_ERROR;
+    }
+    return SQLITE_OK;
+  }
+  static int xClose(sqlite3_vtab_cursor* c) {
+    delete static_cast<typename SubTable::Cursor*>(c);
+    return SQLITE_OK;
+  }
+  static int xFindFunction(sqlite3_vtab* t,
+                           int,
+                           const char* name,
+                           void (**fn)(sqlite3_context*, int, sqlite3_value**),
+                           void** args) {
+    return static_cast<SubTable*>(t)->FindFunction(name, fn, args);
+  }
+  static int xFilter(sqlite3_vtab_cursor* vc,
+                     int i,
+                     const char* s,
+                     int a,
+                     sqlite3_value** v) {
+    auto* cursor = static_cast<typename SubTable::Cursor*>(vc);
+    bool is_cached = cursor->table()->ReadConstraints(i, s, a);
+    auto history = is_cached ? BaseCursor::FilterHistory::kSame
+                             : BaseCursor::FilterHistory::kDifferent;
+    auto* table = static_cast<SubTable*>(cursor->table());
+    return table->SetStatusAndReturn(
+        cursor->Filter(cursor->table()->qc_cache_, v, history));
+  }
+  static int xNext(sqlite3_vtab_cursor* c) {
+    auto* cursor = static_cast<typename SubTable::Cursor*>(c);
+    auto* table = static_cast<SubTable*>(cursor->table());
+    return table->SetStatusAndReturn(cursor->Next());
+  }
+  static int xEof(sqlite3_vtab_cursor* c) {
+    return static_cast<int>(static_cast<typename SubTable::Cursor*>(c)->Eof());
+  }
+  static int xColumn(sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
+    auto* cursor = static_cast<typename SubTable::Cursor*>(c);
+    auto* table = static_cast<SubTable*>(cursor->table());
+    return table->SetStatusAndReturn(cursor->Column(a, b));
+  }
+  static int xRowid(sqlite3_vtab_cursor*, sqlite3_int64*) {
+    return SQLITE_ERROR;
+  }
+  static int xUpdate(sqlite3_vtab* t,
+                     int a,
+                     sqlite3_value** v,
+                     sqlite3_int64* r) {
+    auto* table = static_cast<SubTable*>(t);
+    return table->SetStatusAndReturn(table->Update(a, v, r));
+  }
+};
+
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/sqlite/stats_table.cc b/src/trace_processor/sqlite/stats_table.cc
index e4b7787..296a2f5 100644
--- a/src/trace_processor/sqlite/stats_table.cc
+++ b/src/trace_processor/sqlite/stats_table.cc
@@ -16,6 +16,7 @@
 
 #include "src/trace_processor/sqlite/stats_table.h"
 
+#include "perfetto/base/status.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 
 namespace perfetto {
@@ -24,9 +25,7 @@
 StatsTable::StatsTable(sqlite3*, const TraceStorage* storage)
     : storage_(storage) {}
 
-void StatsTable::RegisterTable(sqlite3* db, const TraceStorage* storage) {
-  SqliteTable::Register<StatsTable>(db, storage, "stats");
-}
+StatsTable::~StatsTable() = default;
 
 util::Status StatsTable::Init(int, const char* const*, Schema* schema) {
   *schema = Schema(
@@ -46,8 +45,8 @@
   return util::OkStatus();
 }
 
-std::unique_ptr<SqliteTable::Cursor> StatsTable::CreateCursor() {
-  return std::unique_ptr<SqliteTable::Cursor>(new Cursor(this));
+std::unique_ptr<SqliteTable::BaseCursor> StatsTable::CreateCursor() {
+  return std::unique_ptr<SqliteTable::BaseCursor>(new Cursor(this));
 }
 
 int StatsTable::BestIndex(const QueryConstraints&, BestIndexInfo*) {
@@ -55,16 +54,20 @@
 }
 
 StatsTable::Cursor::Cursor(StatsTable* table)
-    : SqliteTable::Cursor(table), table_(table), storage_(table->storage_) {}
+    : SqliteTable::BaseCursor(table),
+      table_(table),
+      storage_(table->storage_) {}
 
-int StatsTable::Cursor::Filter(const QueryConstraints&,
-                               sqlite3_value**,
-                               FilterHistory) {
+StatsTable::Cursor::~Cursor() = default;
+
+base::Status StatsTable::Cursor::Filter(const QueryConstraints&,
+                                        sqlite3_value**,
+                                        FilterHistory) {
   *this = Cursor(table_);
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int StatsTable::Cursor::Column(sqlite3_context* ctx, int N) {
+base::Status StatsTable::Cursor::Column(sqlite3_context* ctx, int N) {
   const auto kSqliteStatic = sqlite_utils::kSqliteStatic;
   switch (N) {
     case Column::kName:
@@ -114,16 +117,16 @@
       PERFETTO_FATAL("Unknown column %d", N);
       break;
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int StatsTable::Cursor::Next() {
+base::Status StatsTable::Cursor::Next() {
   static_assert(stats::kTypes[0] == stats::kSingle,
                 "the first stats entry cannot be indexed");
   const auto* cur_entry = &storage_->stats()[key_];
   if (stats::kTypes[key_] == stats::kIndexed) {
     if (++index_ != cur_entry->indexed_values.end()) {
-      return SQLITE_OK;
+      return base::OkStatus();
     }
   }
   while (++key_ < stats::kNumKeys) {
@@ -134,10 +137,10 @@
       break;
     }
   }
-  return SQLITE_OK;
+  return base::OkStatus();
 }
 
-int StatsTable::Cursor::Eof() {
+bool StatsTable::Cursor::Eof() {
   return key_ >= stats::kNumKeys;
 }
 
diff --git a/src/trace_processor/sqlite/stats_table.h b/src/trace_processor/sqlite/stats_table.h
index 3213a2a..2824cb6 100644
--- a/src/trace_processor/sqlite/stats_table.h
+++ b/src/trace_processor/sqlite/stats_table.h
@@ -30,20 +30,22 @@
 // The stats table contains diagnostic info and errors that are either:
 // - Collected at trace time (e.g., ftrace buffer overruns).
 // - Generated at parsing time (e.g., clock events out-of-order).
-class StatsTable : public SqliteTable {
+class StatsTable final
+    : public TypedSqliteTable<StatsTable, const TraceStorage*> {
  public:
   enum Column { kName = 0, kIndex, kSeverity, kSource, kValue, kDescription };
-  class Cursor : public SqliteTable::Cursor {
+  class Cursor final : public SqliteTable::BaseCursor {
    public:
-    Cursor(StatsTable*);
+    explicit Cursor(StatsTable*);
+    ~Cursor() final;
 
     // Implementation of SqliteTable::Cursor.
-    int Filter(const QueryConstraints&,
-               sqlite3_value**,
-               FilterHistory) override;
-    int Next() override;
-    int Eof() override;
-    int Column(sqlite3_context*, int N) override;
+    base::Status Filter(const QueryConstraints&,
+                        sqlite3_value**,
+                        FilterHistory);
+    base::Status Next();
+    bool Eof();
+    base::Status Column(sqlite3_context*, int N);
 
    private:
     Cursor(Cursor&) = delete;
@@ -58,14 +60,13 @@
     TraceStorage::Stats::IndexMap::const_iterator index_{};
   };
 
-  static void RegisterTable(sqlite3* db, const TraceStorage* storage);
-
   StatsTable(sqlite3*, const TraceStorage*);
+  ~StatsTable() final;
 
   // Table implementation.
-  util::Status Init(int, const char* const*, SqliteTable::Schema*) override;
-  std::unique_ptr<SqliteTable::Cursor> CreateCursor() override;
-  int BestIndex(const QueryConstraints&, BestIndexInfo*) override;
+  util::Status Init(int, const char* const*, SqliteTable::Schema*) final;
+  std::unique_ptr<SqliteTable::BaseCursor> CreateCursor() final;
+  int BestIndex(const QueryConstraints&, BestIndexInfo*) final;
 
  private:
   const TraceStorage* const storage_;
diff --git a/src/trace_processor/stdlib/android/BUILD.gn b/src/trace_processor/stdlib/android/BUILD.gn
index 86bdf6d..9596be8 100644
--- a/src/trace_processor/stdlib/android/BUILD.gn
+++ b/src/trace_processor/stdlib/android/BUILD.gn
@@ -18,9 +18,12 @@
   deps = [ "startup" ]
   sources = [
     "battery.sql",
+    "battery_stats.sql",
     "binder.sql",
     "monitor_contention.sql",
+    "network_packets.sql",
     "process_metadata.sql",
     "slices.sql",
+    "statsd.sql",
   ]
 }
diff --git a/src/trace_processor/stdlib/android/battery_stats.sql b/src/trace_processor/stdlib/android/battery_stats.sql
new file mode 100644
index 0000000..2507cb1
--- /dev/null
+++ b/src/trace_processor/stdlib/android/battery_stats.sql
@@ -0,0 +1,210 @@
+--
+-- 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.
+
+SELECT IMPORT('common.timestamps');
+
+-- Converts a battery_stats counter value to human readable string.
+--
+-- @arg track STRING  The counter track name (e.g. 'battery_stats.audio').
+-- @arg value LONG    The counter value.
+-- @ret STRING        The human-readable name for the counter value.
+SELECT CREATE_FUNCTION(
+  'BATTERY_STATS_COUNTER_TO_STRING(track STRING, value FLOAT)',
+  'STRING',
+  '
+  SELECT
+    CASE
+      WHEN ($track = "battery_stats.wifi_scan" OR
+            $track = "battery_stats.wifi_radio" OR
+            $track = "battery_stats.mobile_radio" OR
+            $track = "battery_stats.audio" OR
+            $track = "battery_stats.video" OR
+            $track = "battery_stats.camera" OR
+            $track = "battery_stats.power_save" OR
+            $track = "battery_stats.phone_in_call")
+        THEN
+          CASE $value
+            WHEN 0 THEN "inactive"
+            WHEN 1 THEN "active"
+            ELSE "unknown"
+          END
+      WHEN $track = "battery_stats.wifi"
+        THEN
+          CASE $value
+            WHEN 0 THEN "off"
+            WHEN 1 THEN "on"
+            ELSE "unknown"
+          END
+      WHEN $track = "battery_stats.phone_state"
+        THEN
+          CASE $value
+            WHEN 0 THEN "in"
+            WHEN 1 THEN "out"
+            WHEN 2 THEN "emergency"
+            WHEN 3 THEN "off"
+            ELSE "unknown"
+          END
+      WHEN ($track = "battery_stats.phone_signal_strength" OR
+            $track = "battery_stats.wifi_signal_strength")
+        THEN
+          CASE $value
+            WHEN 0 THEN "none"
+            WHEN 1 THEN "poor"
+            WHEN 2 THEN "moderate"
+            WHEN 3 THEN "good"
+            WHEN 4 THEN "great"
+            ELSE "unknown"
+          END
+      WHEN $track = "battery_stats.wifi_suppl"
+        THEN
+          CASE $value
+            WHEN 0 THEN "invalid"
+            WHEN 1 THEN "disconn"
+            WHEN 2 THEN "disabled"
+            WHEN 3 THEN "inactive"
+            WHEN 4 THEN "scanning"
+            WHEN 5 THEN "authenticating"
+            WHEN 6 THEN "associating"
+            WHEN 7 THEN "associated"
+            WHEN 8 THEN "4-way-handshake"
+            WHEN 9 THEN "group-handshake"
+            WHEN 10 THEN "completed"
+            WHEN 11 THEN "dormant"
+            WHEN 12 THEN "uninit"
+            ELSE "unknown"
+          END
+      WHEN $track = "battery_stats.data_conn"
+        THEN
+          CASE $value
+            WHEN 0 THEN "oos"
+            WHEN 1 THEN "gprs"
+            WHEN 2 THEN "edge"
+            WHEN 3 THEN "umts"
+            WHEN 4 THEN "cdma"
+            WHEN 5 THEN "evdo_0"
+            WHEN 6 THEN "evdo_A"
+            WHEN 7 THEN "1xrtt"
+            WHEN 8 THEN "hsdpa"
+            WHEN 9 THEN "hsupa"
+            WHEN 10 THEN "hspa"
+            WHEN 11 THEN "iden"
+            WHEN 12 THEN "evdo_b"
+            WHEN 13 THEN "lte"
+            WHEN 14 THEN "ehrpd"
+            WHEN 15 THEN "hspap"
+            WHEN 16 THEN "gsm"
+            WHEN 17 THEN "td_scdma"
+            WHEN 18 THEN "iwlan"
+            WHEN 19 THEN "lte_ca"
+            WHEN 20 THEN "nr"
+            WHEN 21 THEN "emngcy"
+            WHEN 22 THEN "other"
+            ELSE "unknown"
+          END
+      ELSE CAST($value AS text)
+    END
+  '
+);
+
+
+-- View of human readable battery stats counter-based states. These are recorded
+-- by BatteryStats as a bitmap where each 'category' has a unique value at any
+-- given time.
+--
+-- @column ts                  Timestamp in nanoseconds.
+-- @column dur                 The duration the state was active.
+-- @column track_name          The name of the counter track.
+-- @column value               The counter value as a number.
+-- @column value_name          The counter value as a human-readable string.
+CREATE VIEW android_battery_stats_state AS
+SELECT
+  ts,
+  name AS track_name,
+  CAST(value AS INT64) AS value,
+  BATTERY_STATS_COUNTER_TO_STRING(name, value) AS value_name,
+  IFNULL(LEAD(ts) OVER (PARTITION BY track_id ORDER BY ts) - ts, -1) AS dur
+FROM counter
+JOIN counter_track
+  ON counter.track_id = counter_track.id
+WHERE counter_track.name GLOB 'battery_stats.*';
+
+
+-- View of slices derived from battery_stats events. Battery stats records all
+-- events as instants, however some may indicate whether something started or
+-- stopped with a '+' or '-' prefix. Events such as jobs, top apps, foreground
+-- apps or long wakes include these details and allow drawing slices between
+-- instant events found in a trace.
+--
+-- For example, we may see an event like the following on 'battery_stats.top':
+--
+--     -top=10215:"com.google.android.apps.nexuslauncher"
+--
+-- This view will find the associated start ('+top') with the matching suffix
+-- (everything after the '=') to construct a slice. It computes the timestamp
+-- and duration from the events and extract the details as follows:
+--
+--     track_name='battery_stats.top'
+--     str_value='com.google.android.apps.nexuslauncher'
+--     int_value=10215
+--
+-- @column track_name          The battery stats track name.
+-- @column ts                  Timestamp in nanoseconds.
+-- @column dur                 The duration of the event.
+-- @column str_value           The string part of the event identifier.
+-- @column int_value           The integer part of the event identifier.
+CREATE VIEW android_battery_stats_event_slices AS
+WITH
+  event_markers AS (
+    SELECT
+      ts,
+      track.name AS track_name,
+      str_split(slice.name, '=', 1) AS key,
+      substr(slice.name, 1, 1) = '+' AS start
+    FROM slice
+    JOIN track
+      ON slice.track_id = track.id
+    WHERE
+      track_name GLOB 'battery_stats.*'
+      AND substr(slice.name, 1, 1) IN ('+', '-')
+  ),
+  with_neighbors AS (
+    SELECT
+      *,
+      LAG(ts) OVER (PARTITION BY track_name, key ORDER BY ts) AS last_ts,
+      LEAD(ts) OVER (PARTITION BY track_name, key ORDER BY ts) AS next_ts
+    FROM event_markers
+  ),
+  -- Note: query performance depends on the ability to push down filters on
+  -- the track_name. It would be more clear below to have two queries and union
+  -- them, but doing so prevents push down through the above window functions.
+  event_spans AS (
+    SELECT
+      track_name, key,
+      IIF(start, ts, TRACE_START()) AS ts,
+      IIF(start, next_ts, ts) AS end_ts
+    FROM with_neighbors
+    -- For the majority of events, we take the `start` event and compute the dur
+    -- based on next_ts. In the off chance we get an end event with no prior
+    -- start (matched by the second half of this where), we can create an event
+    -- starting from the beginning of the trace ending at the current event.
+    WHERE (start OR last_ts IS NULL)
+  )
+SELECT
+  ts,
+  IFNULL(end_ts-ts, -1) AS dur,
+  track_name,
+  str_split(key, '"', 1) AS str_value,
+  CAST(str_split(key, ':', 0) AS INT64) AS int_value
+FROM event_spans;
diff --git a/src/trace_processor/stdlib/android/binder.sql b/src/trace_processor/stdlib/android/binder.sql
index 51a9d75..a31fff0 100644
--- a/src/trace_processor/stdlib/android/binder.sql
+++ b/src/trace_processor/stdlib/android/binder.sql
@@ -90,6 +90,7 @@
       thread.name AS thread_name,
       thread.utid AS utid,
       thread.tid AS tid,
+      process.pid AS pid,
       process.upid AS upid,
       slice.ts,
       slice.dur,
@@ -112,6 +113,7 @@
       reply_process.name AS server_process,
       reply_thread.utid AS server_utid,
       reply_thread.tid AS server_tid,
+      reply_process.pid AS server_pid,
       reply_process.upid AS server_upid,
       aidl.name AS aidl_name
     FROM binder_txn
@@ -132,6 +134,7 @@
   upid AS client_upid,
   utid AS client_utid,
   tid AS client_tid,
+  pid AS client_pid,
   is_main_thread,
   ts AS client_ts,
   dur AS client_dur,
@@ -141,6 +144,7 @@
   server_upid,
   server_utid,
   server_tid,
+  server_pid,
   server_ts,
   server_dur
 FROM binder_reply
diff --git a/src/trace_processor/stdlib/android/network_packets.sql b/src/trace_processor/stdlib/android/network_packets.sql
new file mode 100644
index 0000000..a88615a
--- /dev/null
+++ b/src/trace_processor/stdlib/android/network_packets.sql
@@ -0,0 +1,52 @@
+--
+-- 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.
+
+-- Android network packet events (from android.network_packets data source).
+--
+-- @column ts                  Timestamp in nanoseconds.
+-- @column dur                 Duration (non-zero only in aggregate events)
+-- @column track_name          The track name (interface and direction)
+-- @column package_name        Traffic package source (or uid=$X if not found)
+-- @column iface               Traffic interface name (linux interface name)
+-- @column direction           Traffic direction ('Transmitted' or 'Received')
+-- @column packet_count        Number of packets in this event
+-- @column packet_length       Number of bytes in this event (wire size)
+-- @column packet_transport    Transport used for traffic in this event
+-- @column packet_tcp_flags    TCP flags used by tcp frames in this event
+-- @column socket_tag          The Android traffic tag of the network socket
+-- @column socket_uid          The Linux user id of the network socket
+-- @column local_port          The local port number (for udp or tcp only)
+-- @column remote_port         The remote port number (for udp or tcp only)
+CREATE VIEW android_network_packets AS
+SELECT
+  ts,
+  dur,
+  track.name AS track_name,
+  slice.name AS package_name,
+  str_split(track.name, ' ', 0) AS iface,
+  str_split(track.name, ' ', 1) AS direction,
+  ifnull(extract_arg(arg_set_id, 'packet_count'), 1) AS packet_count,
+  extract_arg(arg_set_id, 'packet_length') AS packet_length,
+  extract_arg(arg_set_id, 'packet_transport') AS packet_transport,
+  extract_arg(arg_set_id, 'packet_tcp_flags') AS packet_tcp_flags,
+  extract_arg(arg_set_id, 'socket_tag') AS socket_tag,
+  extract_arg(arg_set_id, 'socket_uid') AS socket_uid,
+  extract_arg(arg_set_id, 'local_port') AS local_port,
+  extract_arg(arg_set_id, 'remote_port') AS remote_port
+FROM slice
+JOIN track
+  ON slice.track_id = track.id
+WHERE (track.name GLOB '* Transmitted' OR
+       track.name GLOB '* Received');
diff --git a/src/trace_processor/stdlib/android/process_metadata.sql b/src/trace_processor/stdlib/android/process_metadata.sql
index 20fb82e..6154f00 100644
--- a/src/trace_processor/stdlib/android/process_metadata.sql
+++ b/src/trace_processor/stdlib/android/process_metadata.sql
@@ -50,11 +50,19 @@
 LEFT JOIN internal_uid_package_count ON process.android_appid = internal_uid_package_count.uid
 LEFT JOIN package_list plist
   ON (
-    process.android_appid = plist.uid
-    AND internal_uid_package_count.uid = plist.uid
-    AND (
-      -- unique match
-      internal_uid_package_count.cnt = 1
-      -- or process name starts with the package name
-      OR process.name GLOB plist.package_name || '*')
+    (
+      process.android_appid = plist.uid
+      AND internal_uid_package_count.uid = plist.uid
+      AND (
+        -- unique match
+        internal_uid_package_count.cnt = 1
+        -- or process name starts with the package name
+        OR process.name GLOB plist.package_name || '*')
+    )
+    OR
+    (
+      -- isolated processes can only be matched based on the name prefix
+      process.android_appid >= 90000 AND process.android_appid < 100000
+      AND STR_SPLIT(process.name, ':', 0) GLOB plist.package_name || '*'
+    )
   );
diff --git a/src/trace_processor/stdlib/android/statsd.sql b/src/trace_processor/stdlib/android/statsd.sql
new file mode 100644
index 0000000..9ce6bf1
--- /dev/null
+++ b/src/trace_processor/stdlib/android/statsd.sql
@@ -0,0 +1,60 @@
+--
+-- 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.
+--
+
+-- Statsd atoms.
+--
+-- A subset of the slice table containing statsd atom instant events.
+--
+-- @column id,
+-- @column type,
+-- @column ts,
+-- @column dur,
+-- @column arg_set_id,
+-- @column thread_instruction_count,
+-- @column thread_instruction_delta,
+-- @column track_id,
+-- @column category,
+-- @column name,
+-- @column depth,
+-- @column stack_id,
+-- @column parent_stack_id,
+-- @column parent_id,
+-- @column thread_ts,
+-- @column thread_dur,
+CREATE VIEW android_statsd_atoms AS
+SELECT
+  slice.id AS id,
+  slice.type AS type,
+  slice.ts AS ts,
+  slice.dur AS dur,
+  slice.arg_set_id AS arg_set_id,
+  slice.thread_instruction_count AS thread_instruction_count,
+  slice.thread_instruction_delta AS thread_instruction_delta,
+  slice.track_id AS track_id,
+  slice.category AS category,
+  slice.name AS name,
+  slice.depth AS depth,
+  slice.stack_id AS stack_id,
+  slice.parent_stack_id AS parent_stack_id,
+  slice.parent_id AS parent_id,
+  slice.thread_ts AS thread_ts,
+  slice.thread_dur AS thread_dur
+FROM slice
+JOIN track ON slice.track_id = track.id
+WHERE
+  track.name = 'Statsd Atoms';
+
+
diff --git a/src/trace_processor/stdlib/chrome/BUILD.gn b/src/trace_processor/stdlib/chrome/BUILD.gn
index b3ea475..7ba85fd 100644
--- a/src/trace_processor/stdlib/chrome/BUILD.gn
+++ b/src/trace_processor/stdlib/chrome/BUILD.gn
@@ -15,5 +15,8 @@
 import("../../../../gn/perfetto_sql.gni")
 
 perfetto_sql_source_set("chrome_sql") {
-  sources = [ "cpu_powerups.sql" ]
+  sources = [
+    "chrome_scrolls.sql",
+    "cpu_powerups.sql",
+  ]
 }
diff --git a/src/trace_processor/stdlib/chrome/chrome_scrolls.sql b/src/trace_processor/stdlib/chrome/chrome_scrolls.sql
new file mode 100644
index 0000000..1ad7f31
--- /dev/null
+++ b/src/trace_processor/stdlib/chrome/chrome_scrolls.sql
@@ -0,0 +1,68 @@
+-- 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.
+
+DROP VIEW IF EXISTS chrome_scrolls;
+
+-- Defines slices for all of the individual scrolls in a trace based on the
+-- LatencyInfo-based scroll definition.
+--
+-- @column id            The unique identifier of the scroll.
+-- @column ts            The start timestamp of the scroll.
+-- @column dur           The duration of the scroll.
+--
+-- NOTE: this view of top level scrolls is based on the LatencyInfo definition
+-- of a scroll, which differs subtly from the definition based on
+-- EventLatencies.
+-- TODO(b/278684408): add support for tracking scrolls across multiple Chrome/
+-- WebView instances. Currently gesture_scroll_id unique within an instance, but
+-- is not unique across multiple instances. Switching to an EventLatency based
+-- definition of scrolls should resolve this.
+CREATE VIEW chrome_scrolls AS
+WITH all_scrolls AS (
+  SELECT
+    name,
+    ts,
+    dur,
+    extract_arg(arg_set_id, 'chrome_latency_info.gesture_scroll_id') AS scroll_id
+  FROM slice
+  WHERE name GLOB 'InputLatency::GestureScroll*'
+  AND extract_arg(arg_set_id, 'chrome_latency_info.gesture_scroll_id') IS NOT NULL
+),
+scroll_starts AS (
+  SELECT
+    scroll_id,
+    MIN(ts) AS scroll_start_ts
+  FROM all_scrolls
+  WHERE name = 'InputLatency::GestureScrollBegin'
+  GROUP BY scroll_id
+), scroll_ends AS (
+  SELECT
+    scroll_id,
+    MIN(ts) AS scroll_end_ts
+  FROM all_scrolls
+  WHERE name = 'InputLatency::GestureScrollEnd'
+  GROUP BY scroll_id
+)
+SELECT
+  sa.scroll_id AS id,
+  MIN(ts) AS ts,
+  CAST(MAX(ts + dur) - MIN(ts) AS INT) AS dur,
+  IFNULL(ss.scroll_start_ts, -1) AS scroll_start_ts,
+  IFNULL(se.scroll_end_ts, -1) AS scroll_end_ts
+FROM all_scrolls sa
+  LEFT JOIN scroll_starts ss ON
+    sa.scroll_id = ss.scroll_id
+  LEFT JOIN scroll_ends se ON
+    sa.scroll_id = se.scroll_id
+GROUP BY sa.scroll_id;
\ No newline at end of file
diff --git a/src/trace_processor/stdlib/common/BUILD.gn b/src/trace_processor/stdlib/common/BUILD.gn
index 8147bcf..38088fc 100644
--- a/src/trace_processor/stdlib/common/BUILD.gn
+++ b/src/trace_processor/stdlib/common/BUILD.gn
@@ -17,6 +17,7 @@
 perfetto_sql_source_set("common") {
   sources = [
     "counters.sql",
+    "cpus.sql",
     "metadata.sql",
     "percentiles.sql",
     "slices.sql",
diff --git a/src/trace_processor/stdlib/common/cpus.sql b/src/trace_processor/stdlib/common/cpus.sql
new file mode 100644
index 0000000..3caec3f
--- /dev/null
+++ b/src/trace_processor/stdlib/common/cpus.sql
@@ -0,0 +1,60 @@
+--
+-- 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.
+
+
+CREATE TABLE internal_cpu_sizes AS
+SELECT 0 AS n, 'little' AS size
+UNION
+SELECT 1 AS n, 'big' AS size
+UNION
+SELECT 2 AS n, 'huge' AS size;
+
+CREATE TABLE internal_ranked_cpus AS
+SELECT
+ (DENSE_RANK() OVER win) - 1 AS n,
+ cpu
+FROM (
+  SELECT
+    track.cpu AS cpu,
+    MAX(counter.value) AS maxfreq
+  FROM counter
+  JOIN cpu_counter_track AS track
+  ON (counter.track_id = track.id)
+  WHERE track.name = "cpufreq"
+  GROUP BY track.cpu
+)
+WINDOW win AS (ORDER BY maxfreq);
+
+-- Guess size of CPU.
+-- On some multicore devices the cores are heterogeneous and divided
+-- into two or more 'sizes'. In a typical case a device might have 8
+-- cores of which 4 are 'little' (low power & low performance) and 4
+-- are 'big' (high power & high performance). This functions attempts
+-- to map a given CPU index onto the relevant descriptor. For
+-- homogeneous systems this returns NULL.
+--
+-- @arg cpu_index INT   Index of the CPU whose size we will guess.
+-- @ret STRING          A descriptive size ('little', 'big', 'huge', etc) or NULL if we have insufficient information.
+SELECT CREATE_FUNCTION(
+  'GUESS_CPU_SIZE(cpu_index INT)',
+  'STRING',
+  '
+  SELECT
+    IIF((SELECT COUNT(DISTINCT n) FROM internal_ranked_cpus) >= 2, size, null) as size
+  FROM internal_ranked_cpus
+  LEFT JOIN internal_cpu_sizes USING(n)
+  WHERE cpu = $cpu_index;
+  '
+);
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 4fe2071..66fc940 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -513,6 +513,13 @@
   const tables::RawTable& raw_table() const { return raw_table_; }
   tables::RawTable* mutable_raw_table() { return &raw_table_; }
 
+  const tables::FtraceEventTable& ftrace_event_table() const {
+    return ftrace_event_table_;
+  }
+  tables::FtraceEventTable* mutable_ftrace_event_table() {
+    return &ftrace_event_table_;
+  }
+
   const tables::CpuTable& cpu_table() const { return cpu_table_; }
   tables::CpuTable* mutable_cpu_table() { return &cpu_table_; }
 
@@ -895,11 +902,8 @@
 
   SqlStats sql_stats_;
 
-  // Raw events are every ftrace event in the trace. The raw event includes
-  // the timestamp and the pid. The args for the raw event will be in the
-  // args table. This table can be used to generate a text version of the
-  // trace.
   tables::RawTable raw_table_{&string_pool_};
+  tables::FtraceEventTable ftrace_event_table_{&string_pool_, &raw_table_};
 
   tables::CpuTable cpu_table_{&string_pool_};
 
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 83145a4..9ec397f 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -32,12 +32,7 @@
 
 source_set("tables") {
   sources = [
-    "counter_tables.h",
-    "flow_tables.h",
-    "macros.h",
     "macros_internal.h",
-    "profiler_tables.h",
-    "slice_tables.h",
     "table_destructors.cc",
   ]
   deps = [
@@ -54,10 +49,7 @@
 
 source_set("unittests") {
   testonly = true
-  sources = [
-    "macros_unittest.cc",
-    "py_tables_unittest.cc",
-  ]
+  sources = [ "py_tables_unittest.cc" ]
   deps = [
     ":py_tables_unittest",
     ":tables",
@@ -71,10 +63,13 @@
   source_set("benchmarks") {
     testonly = true
     deps = [
-      ":tables",
+      ":py_tables_benchmark",
       "../../../gn:benchmark",
       "../../../gn:default_deps",
     ]
-    sources = [ "macros_benchmark.cc" ]
+    sources = [ "py_tables_benchmark.cc" ]
+  }
+  perfetto_tp_tables("py_tables_benchmark") {
+    sources = [ "py_tables_benchmark.py" ]
   }
 }
diff --git a/src/trace_processor/tables/android_tables.py b/src/trace_processor/tables/android_tables.py
index b77afcb..03dff07 100644
--- a/src/trace_processor/tables/android_tables.py
+++ b/src/trace_processor/tables/android_tables.py
@@ -28,6 +28,7 @@
 from src.trace_processor.tables.metadata_tables import THREAD_TABLE
 
 ANDROID_LOG_TABLE = Table(
+    python_module=__file__,
     class_name="AndroidLogTable",
     sql_name="android_logs",
     columns=[
@@ -54,6 +55,7 @@
         }))
 
 ANDROID_GAME_INTERVENTION_LIST_TABLE = Table(
+    python_module=__file__,
     class_name='AndroidGameInterventionListTable',
     sql_name='android_game_intervention_list',
     columns=[
@@ -126,6 +128,7 @@
         }))
 
 ANDROID_DUMPSTATE_TABLE = Table(
+    python_module=__file__,
     class_name='AndroidDumpstateTable',
     sql_name='android_dumpstate',
     columns=[
diff --git a/src/trace_processor/tables/counter_tables.h b/src/trace_processor/tables/counter_tables.h
deleted file mode 100644
index 69668b2..0000000
--- a/src/trace_processor/tables/counter_tables.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_TABLES_COUNTER_TABLES_H_
-#define SRC_TRACE_PROCESSOR_TABLES_COUNTER_TABLES_H_
-
-#include "src/trace_processor/tables/macros.h"
-#include "src/trace_processor/tables/track_tables_py.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace tables {
-
-// @tablegroup Events
-// @param arg_set_id {@joinable args.arg_set_id}
-#define PERFETTO_TP_COUNTER_TABLE_DEF(NAME, PARENT, C) \
-  NAME(CounterTable, "counter")                        \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                    \
-  C(int64_t, ts, Column::Flag::kSorted)                \
-  C(CounterTrackTable::Id, track_id)                   \
-  C(double, value)                                     \
-  C(std::optional<uint32_t>, arg_set_id)
-
-}  // namespace tables
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_COUNTER_TABLES_H_
diff --git a/src/trace_processor/tables/counter_tables.py b/src/trace_processor/tables/counter_tables.py
index 1dd6495..e882452 100644
--- a/src/trace_processor/tables/counter_tables.py
+++ b/src/trace_processor/tables/counter_tables.py
@@ -26,6 +26,7 @@
 from src.trace_processor.tables.track_tables import COUNTER_TRACK_TABLE
 
 COUNTER_TABLE = Table(
+    python_module=__file__,
     class_name='CounterTable',
     sql_name='counter',
     columns=[
diff --git a/src/trace_processor/tables/flow_tables.h b/src/trace_processor/tables/flow_tables.h
deleted file mode 100644
index a00ee89..0000000
--- a/src/trace_processor/tables/flow_tables.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
-#define SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
-
-#include "src/trace_processor/tables/macros.h"
-#include "src/trace_processor/tables/slice_tables.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace tables {
-
-// @param arg_set_id {@joinable args.arg_set_id}
-#define PERFETTO_TP_FLOW_TABLE_DEF(NAME, PARENT, C) \
-  NAME(FlowTable, "flow")                           \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                 \
-  C(SliceTable::Id, slice_out)                      \
-  C(SliceTable::Id, slice_in)                       \
-  C(uint32_t, arg_set_id)
-
-}  // namespace tables
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_FLOW_TABLES_H_
diff --git a/src/trace_processor/tables/flow_tables.py b/src/trace_processor/tables/flow_tables.py
index 0491a31..df0ae4c 100644
--- a/src/trace_processor/tables/flow_tables.py
+++ b/src/trace_processor/tables/flow_tables.py
@@ -22,6 +22,7 @@
 from src.trace_processor.tables.slice_tables import SLICE_TABLE
 
 FLOW_TABLE = Table(
+    python_module=__file__,
     class_name='FlowTable',
     sql_name='flow',
     columns=[
diff --git a/src/trace_processor/tables/macros.h b/src/trace_processor/tables/macros.h
deleted file mode 100644
index a085066..0000000
--- a/src/trace_processor/tables/macros.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_TABLES_MACROS_H_
-#define SRC_TRACE_PROCESSOR_TABLES_MACROS_H_
-
-#include "src/trace_processor/tables/macros_internal.h"
-
-namespace perfetto {
-namespace trace_processor {
-
-// Usage of the below macros
-// These macros have two different invocation patterns depending on whether you
-// are defining a root table or a derived table (see below for definitions and
-// examples). If you're not sure which one you need, you probably want a derived
-// table.
-//
-// Root tables
-// Root tables act as the ultimate parent of a heirarcy of tables. All rows of
-// child tables will be some subset of rows in the parent. Real world examples
-// of root tables include EventTable and TrackTable.
-//
-// All root tables implicitly contain an 'id' column which contains the row
-// index for each row in the table.
-//
-// Suppose we want to define EventTable with columns 'ts' and 'arg_set_id'.
-//
-// Then we would invoke the macro as follows:
-// #define PERFETTO_TP_EVENT_TABLE_DEF(NAME, PARENT, C)
-//   NAME(EventTable, "event")
-//   PERFETTO_TP_ROOT_TABLE(PARENT, C)
-//   C(int64_t, ts, Column::kSorted)
-//   C(uint32_t, arg_set_id)
-// PERFETTO_TP_TABLE(PERFETTO_TP_EVENT_TABLE_DEF);
-//
-// Note the call to PERFETTO_TP_ROOT_TABLE; this macro (defined below) should
-// be called by root tables passing the PARENT and C and allows for correct type
-// checking of root tables.
-//
-// Derived tables
-// Suppose we want to derive a table called SliceTable which inherits all
-// columns from EventTable (with EventTable's definition macro being
-// PERFETTO_TP_EVENT_TABLE_DEF) and columns 'dur' and 'depth'.
-//
-// Then, we would invoke the macro as follows:
-// #define PERFETTO_TP_SLICE_TABLE_DEF(NAME, PARENT, C)
-//   NAME(SliceTable, "slice")
-//   PARENT(PERFETTO_TP_EVENT_TABLE_DEF, C)
-//   C(int64_t, dur)
-//   C(uint8_t, depth)
-// PERFETTO_TP_TABLE(PERFETTO_TP_SLICE_TABLE_DEF);
-
-// Macro definition using when defining a new root table.
-//
-// This macro should be called by passing PARENT and C in root tables; this
-// allows for correct type-checking of columns.
-//
-// See the top of the file for how this should be used.
-#define PERFETTO_TP_ROOT_TABLE(PARENT, C) \
-  PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)
-
-// The macro used to define storage backed tables.
-// See the top of the file for how this should be used.
-//
-// This macro takes one argument: the full definition of the table; the
-// definition is a function macro taking three arguments:
-// 1. NAME, a function macro taking two argument: the name of the new class
-//    being defined and the name of the table when exposed to SQLite.
-// 2. PARENT, a function macro taking two arguments: a) the definition of
-//    the parent table if this table
-//    is a root table b) C, the third parameter of the macro definition (see
-//    below). For root tables, PARENT and C are passsed to
-//    PERFETTO_TP_ROOT_TABLE instead of PARENT called directly.
-// 3. C, a function macro taking two or three parameters:
-//      a) the type of a column
-//      b) the name of a column
-//      c) (optional) the flags of the column (see Column::Flag
-//         for details).
-//    This macro should be invoked as many times as there are columns in the
-//    table with the information about them.
-#define PERFETTO_TP_TABLE(DEF)                                   \
-  PERFETTO_TP_TABLE_INTERNAL(                                    \
-      PERFETTO_TP_TABLE_NAME(DEF), PERFETTO_TP_TABLE_CLASS(DEF), \
-      PERFETTO_TP_TABLE_CLASS(PERFETTO_TP_PARENT_DEF(DEF)), DEF)
-
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_MACROS_H_
diff --git a/src/trace_processor/tables/macros_internal.h b/src/trace_processor/tables/macros_internal.h
index 059e7b7..b2f1f15 100644
--- a/src/trace_processor/tables/macros_internal.h
+++ b/src/trace_processor/tables/macros_internal.h
@@ -57,30 +57,6 @@
   explicit RootParentTable(std::nullptr_t);
 };
 
-// IdHelper is used to figure out the Id type for a table.
-//
-// We do this using templates with the following algorithm:
-// 1. If the parent class is anything but RootParentTable, the Id of the
-//    table is the same as the Id of the parent.
-// 2. If the parent class is RootParentTable (i.e. the table is a root
-//    table), then the Id is the one defined in the table itself.
-// The net result of this is that all tables in the hierarchy get the
-// same type of Id - the one defined in the root table of that hierarchy.
-//
-// Reasoning: We do this because using uint32_t is very overloaded and
-// having a wrapper type for ids is very helpful to avoid confusion with
-// row indices (especially because ids and row indices often appear in
-// similar places in the codebase - that is at insertion in parsers and
-// in trackers).
-template <typename ParentClass, typename Class>
-struct IdHelper {
-  using Id = typename ParentClass::Id;
-};
-template <typename Class>
-struct IdHelper<RootParentTable, Class> {
-  using Id = typename Class::DefinedId;
-};
-
 // The parent class for all macro generated tables.
 // This class is used to extract common code from the macro tables to reduce
 // code size.
@@ -284,665 +260,6 @@
 };
 
 }  // namespace macros_internal
-
-// Ignore GCC warning about a missing argument for a variadic macro parameter.
-#if defined(__GNUC__) || defined(__clang__)
-#pragma GCC system_header
-#endif
-
-// Basic helper macros.
-#define PERFETTO_TP_NOOP(...)
-
-// Gets the class name from a table definition.
-#define PERFETTO_TP_EXTRACT_TABLE_CLASS(class_name, ...) class_name
-#define PERFETTO_TP_TABLE_CLASS(DEF) \
-  DEF(PERFETTO_TP_EXTRACT_TABLE_CLASS, PERFETTO_TP_NOOP, PERFETTO_TP_NOOP)
-
-// Gets the table name from the table definition.
-#define PERFETTO_TP_EXTRACT_TABLE_NAME(_, table_name) table_name
-#define PERFETTO_TP_TABLE_NAME(DEF) \
-  DEF(PERFETTO_TP_EXTRACT_TABLE_NAME, PERFETTO_TP_NOOP, PERFETTO_TP_NOOP)
-
-// Gets the parent definition from a table definition.
-#define PERFETTO_TP_EXTRACT_PARENT_DEF(PARENT_DEF, _) PARENT_DEF
-#define PERFETTO_TP_PARENT_DEF(DEF) \
-  DEF(PERFETTO_TP_NOOP, PERFETTO_TP_EXTRACT_PARENT_DEF, PERFETTO_TP_NOOP)
-
-// Invokes FN on each column in the definition of the table. We define a
-// recursive macro as we need to walk up the hierarchy until we hit the root.
-// Currently, we hardcode 5 levels but this can be increased as necessary.
-#define PERFETTO_TP_ALL_COLUMNS_0(DEF, arg) \
-  static_assert(false, "Macro recursion depth exceeded");
-#define PERFETTO_TP_ALL_COLUMNS_1(DEF, arg) \
-  DEF(PERFETTO_TP_NOOP, PERFETTO_TP_ALL_COLUMNS_0, arg)
-#define PERFETTO_TP_ALL_COLUMNS_2(DEF, arg) \
-  DEF(PERFETTO_TP_NOOP, PERFETTO_TP_ALL_COLUMNS_1, arg)
-#define PERFETTO_TP_ALL_COLUMNS_3(DEF, arg) \
-  DEF(PERFETTO_TP_NOOP, PERFETTO_TP_ALL_COLUMNS_2, arg)
-#define PERFETTO_TP_ALL_COLUMNS_4(DEF, arg) \
-  DEF(PERFETTO_TP_NOOP, PERFETTO_TP_ALL_COLUMNS_3, arg)
-#define PERFETTO_TP_ALL_COLUMNS(DEF, arg) \
-  DEF(PERFETTO_TP_NOOP, PERFETTO_TP_ALL_COLUMNS_4, arg)
-
-// Invokes FN on each column in the table definition.
-#define PERFETTO_TP_TABLE_COLUMNS(DEF, FN) \
-  DEF(PERFETTO_TP_NOOP, PERFETTO_TP_NOOP, FN)
-
-// Invokes FN on each column in every ancestor of the table.
-#define PERFETTO_TP_PARENT_COLUMNS(DEF, FN) \
-  PERFETTO_TP_ALL_COLUMNS(PERFETTO_TP_PARENT_DEF(DEF), FN)
-
-// Basic macros for extracting column info from a schema.
-#define PERFETTO_TP_NAME_COMMA(type, name, ...) name,
-#define PERFETTO_TP_TYPE_NAME_COMMA(type, name, ...) type name,
-
-// Constructor parameters of Table::Row.
-// We name this name_c to avoid a clash with the field names of
-// Table::Row.
-#define PERFETTO_TP_ROW_CONSTRUCTOR(type, name, ...) type name##_c = {},
-
-// Constructor parameters for parent of Row.
-#define PERFETTO_TP_PARENT_ROW_CONSTRUCTOR(type, name, ...) name##_c,
-
-// Initializes the members of Table::Row.
-#define PERFETTO_TP_ROW_INITIALIZER(type, name, ...) name = name##_c;
-
-// Defines the variable in Table::Row.
-#define PERFETTO_TP_ROW_DEFINITION(type, name, ...) type name = {};
-
-// Used to generate an equality implementation on Table::Row.
-#define PERFETTO_TP_ROW_EQUALS(type, name, ...) \
-  TypedColumn<type>::Equals(other.name, name)&&
-
-// Defines the parent row field in Insert.
-#define PERFETTO_TP_PARENT_ROW_INSERT(type, name, ...) row.name,
-
-// Defines the member variable in the Table.
-#define PERFETTO_TP_TABLE_MEMBER(type, name, ...) \
-  ColumnStorage<TypedColumn<type>::stored_type> name##_;
-
-#define PERFETTO_TP_COLUMN_FLAG_HAS_FLAG_COL(type, name, flags)               \
-  static constexpr uint32_t name##_flags() {                                  \
-    return static_cast<uint32_t>(flags) | TypedColumn<type>::default_flags(); \
-  }
-
-#define PERFETTO_TP_COLUMN_FLAG_NO_FLAG_COL(type, name) \
-  static constexpr uint32_t name##_flags() {            \
-    return TypedColumn<type>::default_flags();          \
-  }
-
-#define PERFETTO_TP_PARENT_COLUMN_FLAG_HAS_FLAG_COL(type, name, flags) \
-  static constexpr uint32_t name##_flags() {                           \
-    return (static_cast<uint32_t>(flags) |                             \
-            TypedColumn<type>::default_flags()) &                      \
-           ~Column::kNoCrossTableInheritFlags;                         \
-  }
-
-#define PERFETTO_TP_PARENT_COLUMN_FLAG_NO_FLAG_COL(type, name) \
-  static constexpr uint32_t name##_flags() {                   \
-    return TypedColumn<type>::default_flags() &                \
-           ~Column::kNoCrossTableInheritFlags;                 \
-  }
-
-#define PERFETTO_TP_COLUMN_FLAG_CHOOSER(type, name, maybe_flags, fn, ...) fn
-
-// MSVC has slightly different rules about __VA_ARGS__ expansion. This makes it
-// behave similarly to GCC/Clang.
-// See https://stackoverflow.com/q/5134523/14028266 .
-#define PERFETTO_TP_EXPAND_VA_ARGS(x) x
-
-#define PERFETTO_TP_COLUMN_FLAG(...)                          \
-  PERFETTO_TP_EXPAND_VA_ARGS(PERFETTO_TP_COLUMN_FLAG_CHOOSER( \
-      __VA_ARGS__, PERFETTO_TP_COLUMN_FLAG_HAS_FLAG_COL,      \
-      PERFETTO_TP_COLUMN_FLAG_NO_FLAG_COL)(__VA_ARGS__))
-
-#define PERFETTO_TP_PARENT_COLUMN_FLAG(...)                     \
-  PERFETTO_TP_EXPAND_VA_ARGS(PERFETTO_TP_COLUMN_FLAG_CHOOSER(   \
-      __VA_ARGS__, PERFETTO_TP_PARENT_COLUMN_FLAG_HAS_FLAG_COL, \
-      PERFETTO_TP_PARENT_COLUMN_FLAG_NO_FLAG_COL)(__VA_ARGS__))
-
-// Creates the sparse vector with the given flags.
-#define PERFETTO_TP_TABLE_CONSTRUCTOR_SV(type, name, ...)        \
-  name##_(ColumnStorage<TypedColumn<type>::stored_type>::Create< \
-          (name##_flags() & Column::Flag::kDense) != 0>()),
-
-// Invokes the chosen column constructor by passing the given args.
-#define PERFETTO_TP_TABLE_CONSTRUCTOR_COLUMN(type, name, ...)   \
-  columns_.emplace_back(#name, &name##_, name##_flags(), this,  \
-                        static_cast<uint32_t>(columns_.size()), \
-                        static_cast<uint32_t>(overlays_.size()) - 1);
-
-// Inserts the value into the corresponding column.
-#define PERFETTO_TP_COLUMN_APPEND(type, name, ...) \
-  mutable_##name()->Append(std::move(row.name));
-
-// Creates a schema entry for the corresponding column.
-#define PERFETTO_TP_COLUMN_SCHEMA(type, name, ...)               \
-  schema.columns.emplace_back(Table::Schema::Column{             \
-      #name, TypedColumn<type>::SqlValueType(), false,           \
-      static_cast<bool>(name##_flags() & Column::Flag::kSorted), \
-      static_cast<bool>(name##_flags() & Column::Flag::kHidden), \
-      static_cast<bool>(name##_flags() & Column::Flag::kSetId)});
-
-// Defines the immutable accessor for a column.
-#define PERFETTO_TP_TABLE_COL_GETTER(type, name, ...)                          \
-  const TypedColumn<type>& name() const {                                      \
-    return static_cast<const TypedColumn<type>&>(columns_[ColumnIndex::name]); \
-  }
-
-// Defines the accessors for a column.
-#define PERFETTO_TP_TABLE_MUTABLE_COL_GETTER(type, name, ...)             \
-  TypedColumn<type>* mutable_##name() {                                   \
-    return static_cast<TypedColumn<type>*>(&columns_[ColumnIndex::name]); \
-  }
-
-// Defines the accessors for a column.
-#define PERFETTO_TP_TABLE_STATIC_ASSERT_FLAG(type, name, ...)                \
-  static_assert(Column::IsFlagsAndTypeValid<TypedColumn<type>::stored_type>( \
-                    name##_flags()),                                         \
-                "Column type and flag combination is not valid");
-
-// Defines the parameter for the |ExtendParent| function.
-#define PERFETTO_TP_TABLE_EXTEND_PARAM(type, name, ...) \
-  ColumnStorage<TypedColumn<type>::stored_type> name,
-
-// Defines the parameter passing for the |ExtendParent| function.
-#define PERFETTO_TP_TABLE_EXTEND_PARAM_PASSING(type, name, ...) std::move(name),
-
-// Sets the table nullable vector to the parameter passed in the
-// |SelectAndExtendParent| function.
-#define PERFETTO_TP_TABLE_EXTEND_SET_NV(type, name, ...) \
-  PERFETTO_DCHECK(name.size() == parent_overlay.size()); \
-  name##_ = std::move(name);
-
-// Definition used as the parent of root tables.
-#define PERFETTO_TP_ROOT_TABLE_PARENT_DEF(NAME, PARENT, C) \
-  NAME(macros_internal::RootParentTable, "root")
-
-// Defines the getter for the column value in the RowReference.
-#define PERFETTO_TP_TABLE_CONST_ROW_REF_GETTER(type, name, ...) \
-  type name() const { return table_->name()[row_number_]; }
-
-// Defines the accessor for the column value in the RowReference.
-#define PERFETTO_TP_TABLE_ROW_REF_SETTER(type, name, ...)          \
-  void set_##name(TypedColumn<type>::non_optional_type v) const {  \
-    return mutable_table()->mutable_##name()->Set(row_number_, v); \
-  }
-
-// Defines the getter for the column value in the ConstIterator.
-#define PERFETTO_TP_TABLE_CONST_IT_GETTER(type, name, ...)  \
-  type name() const {                                       \
-    const auto& col = table_->name();                       \
-    return col.GetAtIdx(its_[col.overlay_index()].index()); \
-  }
-
-// Defines the setter for the column value in the Iterator.
-#define PERFETTO_TP_TABLE_IT_SETTER(type, name, ...)        \
-  void set_##name(TypedColumn<type>::non_optional_type v) { \
-    auto* col = mutable_table_->mutable_##name();           \
-    col->SetAtIdx(its_[col->overlay_index()].index(), v);   \
-  }
-
-// Defines the column index constexpr declaration.
-#define PERFETTO_TP_COLUMN_INDEX(type, name, ...) \
-  static constexpr uint32_t name = static_cast<uint32_t>(ColumnIndexEnum::name);
-
-// Defines an alias for column type for each column.
-#define PERFETTO_TP_COLUMN_TYPE_USING(type, name, ...) \
-  using name = TypedColumn<type>;
-
-// Calls ShrinkToFit on each column.
-#define PERFETTO_TP_COLUMN_SHRINK_TO_FIT(type, name, ...) name##_.ShrinkToFit();
-
-// For more general documentation, see PERFETTO_TP_TABLE in macros.h.
-#define PERFETTO_TP_TABLE_INTERNAL(table_name, class_name, parent_class_name, \
-                                   DEF)                                       \
-  class class_name : public macros_internal::MacroTable {                     \
-   public:                                                                    \
-    /* Forward declaration to allow free usage below. */                      \
-    class ConstRowReference;                                                  \
-    class RowReference;                                                       \
-    class RowNumber;                                                          \
-    class ConstIterator;                                                      \
-                                                                              \
-   private:                                                                   \
-    /*                                                                        \
-     * Allows IdHelper to access DefinedId for root tables.                   \
-     * Needs to be defined here to allow the public using declaration of Id   \
-     * below to work correctly.                                               \
-     */                                                                       \
-    friend struct macros_internal::IdHelper<parent_class_name, class_name>;   \
-                                                                              \
-    /* Whether or not this is a root table */                                 \
-    static constexpr bool kIsRootTable =                                      \
-        std::is_same<parent_class_name,                                       \
-                     macros_internal::RootParentTable>::value;                \
-                                                                              \
-    /* Aliases to reduce clutter in class defintions below. */                \
-    using AbstractRowNumber = macros_internal::                               \
-        AbstractRowNumber<class_name, ConstRowReference, RowReference>;       \
-    using AbstractConstRowReference =                                         \
-        macros_internal::AbstractConstRowReference<class_name, RowNumber>;    \
-    using AbstractConstIterator =                                             \
-        macros_internal::AbstractConstIterator<ConstIterator,                 \
-                                               class_name,                    \
-                                               RowNumber,                     \
-                                               ConstRowReference>;            \
-                                                                              \
-    enum class ColumnIndexEnum {                                              \
-      id,                                                                     \
-      type, /* Expands to col1, col2, ... */                                  \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_NAME_COMMA) kNumCols           \
-    };                                                                        \
-                                                                              \
-    /*                                                                        \
-     * Defines a new id type for a hierarchy of tables.                       \
-     * We define it here as we need this type to be visible for the public    \
-     * using declaration of Id below.                                         \
-     * Note: This type will only used if this table is a root table.          \
-     */                                                                       \
-    struct DefinedId : public BaseId {                                        \
-      DefinedId() = default;                                                  \
-      explicit constexpr DefinedId(uint32_t v) : BaseId(v) {}                 \
-    };                                                                        \
-    static_assert(std::is_trivially_destructible<DefinedId>::value,           \
-                  "Inheritance used without trivial destruction");            \
-                                                                              \
-    static constexpr uint32_t id_flags() { return Column::kIdFlags; }         \
-    static constexpr uint32_t type_flags() { return Column::kNoFlag; }        \
-    PERFETTO_TP_PARENT_COLUMNS(DEF, PERFETTO_TP_PARENT_COLUMN_FLAG)           \
-    PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_COLUMN_FLAG)                   \
-                                                                              \
-   public:                                                                    \
-    /*                                                                        \
-     * This defines the type of the id to be the type of the root             \
-     * table of the hierarchy - see IdHelper for more details.                \
-     */                                                                       \
-    using Id = macros_internal::IdHelper<parent_class_name, class_name>::Id;  \
-                                                                              \
-    struct ColumnIndex {                                                      \
-      static constexpr uint32_t id =                                          \
-          static_cast<uint32_t>(ColumnIndexEnum::id);                         \
-      static constexpr uint32_t type =                                        \
-          static_cast<uint32_t>(ColumnIndexEnum::type);                       \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_COLUMN_INDEX)                  \
-    };                                                                        \
-                                                                              \
-    struct ColumnType {                                                       \
-      using id = IdColumn<Id>;                                                \
-      using type = TypedColumn<StringPool::Id>;                               \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_COLUMN_TYPE_USING)             \
-    };                                                                        \
-                                                                              \
-    struct Row : parent_class_name::Row {                                     \
-      /*                                                                      \
-       * Expands to Row(col_type1 col1_c, std::optional<col_type2> col2_c,    \
-       * ...)                                                                 \
-       */                                                                     \
-      Row(PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_ROW_CONSTRUCTOR)           \
-              std::nullptr_t = nullptr)                                       \
-          : parent_class_name::Row(PERFETTO_TP_PARENT_COLUMNS(                \
-                DEF,                                                          \
-                PERFETTO_TP_PARENT_ROW_CONSTRUCTOR) nullptr) {                \
-        type_ = table_name;                                                   \
-                                                                              \
-        /*                                                                    \
-         * Expands to                                                         \
-         * col1 = col1_c;                                                     \
-         * ...                                                                \
-         */                                                                   \
-        PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_ROW_INITIALIZER)           \
-      }                                                                       \
-                                                                              \
-      bool operator==(const class_name::Row& other) const {                   \
-        return PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_ROW_EQUALS) true;     \
-      }                                                                       \
-                                                                              \
-      /*                                                                      \
-       * Expands to                                                           \
-       * col_type1 col1 = {};                                                 \
-       * ...                                                                  \
-       */                                                                     \
-      PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_ROW_DEFINITION)              \
-    };                                                                        \
-    static_assert(std::is_trivially_destructible<Row>::value,                 \
-                  "Inheritance used without trivial destruction");            \
-                                                                              \
-    /*                                                                        \
-     * Reference to a row which exists in the table.                          \
-     *                                                                        \
-     * Allows caller code to store and instances of this object without       \
-     * having to interact with row numbers.                                   \
-     */                                                                       \
-    class ConstRowReference : public AbstractConstRowReference {              \
-     public:                                                                  \
-      ConstRowReference(const class_name* table, uint32_t row_number)         \
-          : AbstractConstRowReference(table, row_number) {}                   \
-                                                                              \
-      PERFETTO_TP_TABLE_CONST_ROW_REF_GETTER(Id, id)                          \
-      PERFETTO_TP_TABLE_CONST_ROW_REF_GETTER(StringPool::Id, type)            \
-                                                                              \
-      /*                                                                      \
-       * Expands to                                                           \
-       * col1_type col1() const { return table_->col1()[row_]; }              \
-       * ...                                                                  \
-       */                                                                     \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_CONST_ROW_REF_GETTER)    \
-    };                                                                        \
-    static_assert(std::is_trivially_destructible<ConstRowReference>::value,   \
-                  "Inheritance used without trivial destruction");            \
-                                                                              \
-    /*                                                                        \
-     * Reference to a row which exists in the table.                          \
-     *                                                                        \
-     * Allows caller code to store and instances of this object without       \
-     * having to interact with row numbers.                                   \
-     */                                                                       \
-    class RowReference : public ConstRowReference {                           \
-     public:                                                                  \
-      RowReference(class_name* table, uint32_t row_number)                    \
-          : ConstRowReference(table, row_number) {}                           \
-                                                                              \
-      /*                                                                      \
-       * Expands to                                                           \
-       * void set_col1(col1_type v) { table_->mutable_col1()->Set(row, v); }  \
-       * ...                                                                  \
-       */                                                                     \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_ROW_REF_SETTER)          \
-                                                                              \
-     private:                                                                 \
-      class_name* mutable_table() const {                                     \
-        return const_cast<class_name*>(table_);                               \
-      }                                                                       \
-    };                                                                        \
-    static_assert(std::is_trivially_destructible<RowReference>::value,        \
-                  "Inheritance used without trivial destruction");            \
-                                                                              \
-    /*                                                                        \
-     * Strongly typed wrapper around the row index. Prefer storing this over  \
-     * storing RowReference to reduce memory usage                            \
-     */                                                                       \
-    class RowNumber : public AbstractRowNumber {                              \
-     public:                                                                  \
-      explicit RowNumber(uint32_t row_number)                                 \
-          : AbstractRowNumber(row_number) {}                                  \
-    };                                                                        \
-    static_assert(std::is_trivially_destructible<RowNumber>::value,           \
-                  "Inheritance used without trivial destruction");            \
-                                                                              \
-    /* Return value of Insert giving access to id and row number */           \
-    struct IdAndRow {                                                         \
-      Id id;                                                                  \
-      uint32_t row;                                                           \
-      RowReference row_reference;                                             \
-      RowNumber row_number;                                                   \
-    };                                                                        \
-                                                                              \
-    /*                                                                        \
-     * Strongly typed const iterator for this macro table.                    \
-     *                                                                        \
-     * Allows efficient retrieval of values from this table without having to \
-     * deal with row numbers, ColumnStorageOverlays or indices.               \
-     */                                                                       \
-    class ConstIterator : public AbstractConstIterator {                      \
-     public:                                                                  \
-      PERFETTO_TP_TABLE_CONST_IT_GETTER(Id, id)                               \
-      PERFETTO_TP_TABLE_CONST_IT_GETTER(StringPool::Id, type)                 \
-                                                                              \
-      /*                                                                      \
-       * Expands to                                                           \
-       * col1_type col1() const { return table_->col1().GetAtIdx(i); }        \
-       * ...                                                                  \
-       */                                                                     \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_CONST_IT_GETTER)         \
-                                                                              \
-     protected:                                                               \
-      /*                                                                      \
-       * Must not be public to avoid buggy code because of inheritance        \
-       * without virtual destructor.                                          \
-       */                                                                     \
-      explicit ConstIterator(const class_name* table,                         \
-                             std::vector<ColumnStorageOverlay> overlays)      \
-          : AbstractConstIterator(table, std::move(overlays)) {}              \
-                                                                              \
-      uint32_t CurrentRowNumber() const {                                     \
-        /*                                                                    \
-         * Because the last ColumnStorageOverlay belongs to this table it     \
-         * will be dense (i.e. every row in the table will be part of this    \
-         * ColumnStorageOverlay + will be represented with a range). This     \
-         * means that the index() of the last ColumnStorageOverlay iterator   \
-         * is precisely the row number in table!                              \
-         */                                                                   \
-        return its_.back().index();                                           \
-      }                                                                       \
-                                                                              \
-     private:                                                                 \
-      friend class class_name;                                                \
-      friend class AbstractConstIterator;                                     \
-    };                                                                        \
-                                                                              \
-    /*                                                                        \
-     * Strongly typed iterator for this macro table.                          \
-     *                                                                        \
-     * Enhances ConstIterator by also allowing values in the table to be set  \
-     * as well as retrieved.                                                  \
-     */                                                                       \
-    class Iterator : public ConstIterator {                                   \
-     public:                                                                  \
-      /*                                                                      \
-       * Expands to                                                           \
-       * void set_col1(col1_type v) { table_->mut_col1()->SetAtIdx(i, v); }   \
-       * ...                                                                  \
-       */                                                                     \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_IT_SETTER)               \
-                                                                              \
-      /*                                                                      \
-       * Returns a RowReference to the current row.                           \
-       */                                                                     \
-      RowReference row_reference() const {                                    \
-        return RowReference(mutable_table_, CurrentRowNumber());              \
-      }                                                                       \
-                                                                              \
-     private:                                                                 \
-      friend class class_name;                                                \
-                                                                              \
-      /*                                                                      \
-       * Must not be public to avoid buggy code because of inheritance        \
-       * without virtual destructor.                                          \
-       */                                                                     \
-      explicit Iterator(class_name* table,                                    \
-                        std::vector<ColumnStorageOverlay> overlays)           \
-          : ConstIterator(table, std::move(overlays)),                        \
-            mutable_table_(table) {}                                          \
-                                                                              \
-      class_name* mutable_table_ = nullptr;                                   \
-    };                                                                        \
-                                                                              \
-    class_name(StringPool* pool, parent_class_name* parent)                   \
-        : macros_internal::MacroTable(pool, parent),                          \
-          PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_TABLE_CONSTRUCTOR_SV)    \
-              parent_(parent) {                                               \
-      PERFETTO_CHECK(kIsRootTable == (parent == nullptr));                    \
-                                                                              \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_STATIC_ASSERT_FLAG)      \
-                                                                              \
-      /*                                                                      \
-       * Expands to                                                           \
-       * columns_.emplace_back("col1", col1_, Column::kNoFlag, this,          \
-       *                       static_cast<uint32_t>(columns_.size()),        \
-       *                       static_cast<uint32_t>(overlays_.size()) - 1);  \
-       * ...                                                                  \
-       */                                                                     \
-      PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_TABLE_CONSTRUCTOR_COLUMN);   \
-    }                                                                         \
-    ~class_name() override;                                                   \
-                                                                              \
-    IdAndRow Insert(const Row& row) {                                         \
-      PERFETTO_DCHECK(allow_inserts_);                                        \
-                                                                              \
-      Id id;                                                                  \
-      uint32_t row_number = row_count();                                      \
-      if (kIsRootTable) {                                                     \
-        id = Id{row_number};                                                  \
-        type_.Append(string_pool_->InternString(row.type()));                 \
-      } else {                                                                \
-        PERFETTO_DCHECK(parent_);                                             \
-        id = Id{parent_->Insert(row).id};                                     \
-        UpdateOverlaysAfterParentInsert();                                    \
-      }                                                                       \
-                                                                              \
-      /*                                                                      \
-       * Expands to                                                           \
-       * col1_.Append(row.col1);                                              \
-       * ...                                                                  \
-       */                                                                     \
-      PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_COLUMN_APPEND);              \
-                                                                              \
-      UpdateSelfOverlayAfterInsert();                                         \
-      return {id, row_number, RowReference(this, row_number),                 \
-              RowNumber(row_number)};                                         \
-    }                                                                         \
-                                                                              \
-    static Table::Schema ComputeStaticSchema() {                              \
-      Table::Schema schema;                                                   \
-      schema.columns.emplace_back(Table::Schema::Column{                      \
-          "id", SqlValue::Type::kLong, true, true, false, false});            \
-      schema.columns.emplace_back(Table::Schema::Column{                      \
-          "type", SqlValue::Type::kString, false, false, false, false});      \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_COLUMN_SCHEMA);                \
-      return schema;                                                          \
-    }                                                                         \
-                                                                              \
-    void ShrinkToFit() {                                                      \
-      type_.ShrinkToFit();                                                    \
-      PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_COLUMN_SHRINK_TO_FIT);       \
-    }                                                                         \
-                                                                              \
-    /* Iterates the table. */                                                 \
-    ConstIterator IterateRows() const {                                       \
-      return ConstIterator(this, CopyOverlays());                             \
-    }                                                                         \
-                                                                              \
-    /* Iterates the table. */                                                 \
-    Iterator IterateRows() { return Iterator(this, CopyOverlays()); }         \
-                                                                              \
-    /* Filters the Table using the specified filter constraints. */           \
-    ConstIterator FilterToIterator(                                           \
-        const std::vector<Constraint>& cs,                                    \
-        RowMap::OptimizeFor opt = RowMap::OptimizeFor::kMemory) const {       \
-      return ConstIterator(this, FilterAndApplyToOverlays(cs, opt));          \
-    }                                                                         \
-                                                                              \
-    /* Filters the Table using the specified filter constraints. */           \
-    Iterator FilterToIterator(                                                \
-        const std::vector<Constraint>& cs,                                    \
-        RowMap::OptimizeFor opt = RowMap::OptimizeFor::kMemory) {             \
-      return Iterator(this, FilterAndApplyToOverlays(cs, opt));               \
-    }                                                                         \
-                                                                              \
-    /* Returns a ConstRowReference to the row pointed to by |find_id|. */     \
-    std::optional<ConstRowReference> FindById(Id find_id) const {             \
-      std::optional<uint32_t> row = id().IndexOf(find_id);                    \
-      if (!row)                                                               \
-        return std::nullopt;                                                  \
-      return ConstRowReference(this, *row);                                   \
-    }                                                                         \
-                                                                              \
-    /* Returns a RowReference to the row pointed to by |find_id|. */          \
-    std::optional<RowReference> FindById(Id find_id) {                        \
-      std::optional<uint32_t> row = id().IndexOf(find_id);                    \
-      if (!row)                                                               \
-        return std::nullopt;                                                  \
-      return RowReference(this, *row);                                        \
-    }                                                                         \
-                                                                              \
-    const IdColumn<Id>& id() const {                                          \
-      return static_cast<const IdColumn<Id>&>(                                \
-          columns_[static_cast<uint32_t>(ColumnIndex::id)]);                  \
-    }                                                                         \
-    PERFETTO_TP_TABLE_COL_GETTER(StringPool::Id, type)                        \
-                                                                              \
-    /* Returns the name of the table */                                       \
-    static constexpr const char* Name() { return table_name; }                \
-                                                                              \
-    /*                                                                        \
-     * Creates a filled instance of this class by selecting all rows in       \
-     * parent and filling the table columns with the provided vectors.        \
-     */                                                                       \
-    static std::unique_ptr<Table> ExtendParent(                               \
-        const parent_class_name& parent,                                      \
-        PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_TABLE_EXTEND_PARAM)        \
-            std::nullptr_t = nullptr) {                                       \
-      return std::unique_ptr<Table>(new class_name(                           \
-          parent.string_pool(), parent, RowMap(0, parent.row_count()),        \
-          PERFETTO_TP_TABLE_COLUMNS(                                          \
-              DEF, PERFETTO_TP_TABLE_EXTEND_PARAM_PASSING) nullptr));         \
-    }                                                                         \
-                                                                              \
-    /*                                                                        \
-     * Creates a filled instance of this class by first selecting all rows in \
-     * parent given by |rows| and filling the table columns with the provided \
-     * vectors.                                                               \
-     */                                                                       \
-    static std::unique_ptr<Table> SelectAndExtendParent(                      \
-        const parent_class_name& parent,                                      \
-        std::vector<parent_class_name::RowNumber> parent_row_overlay,         \
-        PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_TABLE_EXTEND_PARAM)        \
-            std::nullptr_t = nullptr) {                                       \
-      std::vector<uint32_t> prs_untyped(parent_row_overlay.size());           \
-      for (uint32_t i = 0; i < parent_row_overlay.size(); ++i) {              \
-        prs_untyped[i] = parent_row_overlay[i].row_number();                  \
-      }                                                                       \
-      return std::unique_ptr<Table>(new class_name(                           \
-          parent.string_pool(), parent, RowMap(std::move(prs_untyped)),       \
-          PERFETTO_TP_TABLE_COLUMNS(                                          \
-              DEF, PERFETTO_TP_TABLE_EXTEND_PARAM_PASSING) nullptr));         \
-    }                                                                         \
-                                                                              \
-    /*                                                                        \
-     * Expands to                                                             \
-     * const TypedColumn<col1_type>& col1() { return col1_; }                 \
-     * ...                                                                    \
-     */                                                                       \
-    PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_COL_GETTER)                \
-                                                                              \
-    /*                                                                        \
-     * Expands to                                                             \
-     * TypedColumn<col1_type>* mutable_col1() { return &col1_; }              \
-     * ...                                                                    \
-     */                                                                       \
-    PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_MUTABLE_COL_GETTER)        \
-                                                                              \
-   private:                                                                   \
-    class_name(StringPool* pool,                                              \
-               const parent_class_name& parent,                               \
-               RowMap parent_overlay,                                         \
-               PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_TABLE_EXTEND_PARAM) \
-                   std::nullptr_t = nullptr)                                  \
-        : macros_internal::MacroTable(pool, parent, parent_overlay) {         \
-      PERFETTO_TP_ALL_COLUMNS(DEF, PERFETTO_TP_TABLE_STATIC_ASSERT_FLAG)      \
-      PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_TABLE_EXTEND_SET_NV)         \
-                                                                              \
-      /*                                                                      \
-       * Expands to                                                           \
-       * columns_.emplace_back("col1", col1_, Column::kNoFlag, this,          \
-       *                       static_cast<uint32_t>(columns_.size()),        \
-       *                       static_cast<uint32_t>(overlays_.size()) - 1);  \
-       * ...                                                                  \
-       */                                                                     \
-      PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_TABLE_CONSTRUCTOR_COLUMN);   \
-    }                                                                         \
-                                                                              \
-    /*                                                                        \
-     * Expands to                                                             \
-     * NullableVector<col1_type> col1_;                                       \
-     * ...                                                                    \
-     */                                                                       \
-    PERFETTO_TP_TABLE_COLUMNS(DEF, PERFETTO_TP_TABLE_MEMBER)                  \
-                                                                              \
-    parent_class_name* parent_ = nullptr;                                     \
-  }
-
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/tables/memory_tables.py b/src/trace_processor/tables/memory_tables.py
index 65263f8..f270aa7 100644
--- a/src/trace_processor/tables/memory_tables.py
+++ b/src/trace_processor/tables/memory_tables.py
@@ -26,6 +26,7 @@
 from src.trace_processor.tables.track_tables import TRACK_TABLE
 
 MEMORY_SNAPSHOT_TABLE = Table(
+    python_module=__file__,
     class_name='MemorySnapshotTable',
     sql_name='memory_snapshot',
     columns=[
@@ -43,6 +44,7 @@
         }))
 
 PROCESS_MEMORY_SNAPSHOT_TABLE = Table(
+    python_module=__file__,
     class_name='ProcessMemorySnapshotTable',
     sql_name='process_memory_snapshot',
     columns=[
@@ -58,6 +60,7 @@
         }))
 
 MEMORY_SNAPSHOT_NODE_TABLE = Table(
+    python_module=__file__,
     class_name='MemorySnapshotNodeTable',
     sql_name='memory_snapshot_node',
     columns=[
@@ -81,6 +84,7 @@
         }))
 
 MEMORY_SNAPSHOT_EDGE_TABLE = Table(
+    python_module=__file__,
     class_name='MemorySnapshotEdgeTable',
     sql_name='memory_snapshot_edge',
     columns=[
diff --git a/src/trace_processor/tables/metadata_tables.py b/src/trace_processor/tables/metadata_tables.py
index 621f3fc..0219aa9 100644
--- a/src/trace_processor/tables/metadata_tables.py
+++ b/src/trace_processor/tables/metadata_tables.py
@@ -29,6 +29,7 @@
 from python.generators.trace_processor_table.public import WrappingSqlView
 
 PROCESS_TABLE = Table(
+    python_module=__file__,
     class_name='ProcessTable',
     sql_name='internal_process',
     columns=[
@@ -100,6 +101,7 @@
         }))
 
 THREAD_TABLE = Table(
+    python_module=__file__,
     class_name='ThreadTable',
     sql_name='internal_thread',
     columns=[
@@ -159,27 +161,59 @@
         }))
 
 RAW_TABLE = Table(
+    python_module=__file__,
     class_name='RawTable',
     sql_name='raw',
     columns=[
         C('ts', CppInt64(), flags=ColumnFlag.SORTED),
         C('name', CppString()),
         C('cpu', CppUint32()),
-        C('utid', CppUint32()),
+        C('utid', CppTableId(THREAD_TABLE)),
         C('arg_set_id', CppUint32()),
     ],
     tabledoc=TableDoc(
-        doc='''''',
+        doc='''
+          Contains 'raw' events from the trace for some types of events. This
+          table only exists for debugging purposes and should not be relied on
+          in production usecases (i.e. metrics, standard library etc).
+        ''',
         group='Misc',
         columns={
-            'arg_set_id': '''''',
-            'ts': '''''',
-            'name': '''''',
-            'cpu': '''''',
-            'utid': ''''''
+            'arg_set_id':
+                ColumnDoc(
+                    'The set of key/value pairs associated with this event.',
+                    joinable='args.arg_set_id'),
+            'ts':
+                'The timestamp of this event.',
+            'name':
+                '''
+                  The name of the event. For ftrace events, this will be the
+                  ftrace event name.
+                ''',
+            'cpu':
+                'The CPU this event was emitted on.',
+            'utid':
+                'The thread this event was emitted on.'
         }))
 
+FTRACE_EVENT_TABLE = Table(
+    python_module=__file__,
+    class_name='FtraceEventTable',
+    sql_name='ftrace_event',
+    parent=RAW_TABLE,
+    columns=[],
+    tabledoc=TableDoc(
+        doc='''
+      Contains all the ftrace events in the trace. This table exists only for
+      debugging purposes and should not be relied on in production usecases
+      (i.e. metrics, standard library etc). Note also that this table might
+      be empty if raw ftrace parsing has been disabled.
+    ''',
+        group='Misc',
+        columns={}))
+
 ARG_TABLE = Table(
+    python_module=__file__,
     class_name='ArgTable',
     sql_name='internal_args',
     columns=[
@@ -206,6 +240,7 @@
         }))
 
 METADATA_TABLE = Table(
+    python_module=__file__,
     class_name='MetadataTable',
     sql_name='metadata',
     columns=[
@@ -225,6 +260,7 @@
         }))
 
 FILEDESCRIPTOR_TABLE = Table(
+    python_module=__file__,
     class_name='FiledescriptorTable',
     sql_name='filedescriptor',
     columns=[
@@ -261,6 +297,7 @@
         }))
 
 EXP_MISSING_CHROME_PROC_TABLE = Table(
+    python_module=__file__,
     class_name='ExpMissingChromeProcTable',
     sql_name='experimental_missing_chrome_processes',
     columns=[
@@ -278,6 +315,7 @@
         }))
 
 CPU_TABLE = Table(
+    python_module=__file__,
     class_name='CpuTable',
     sql_name='cpu',
     columns=[
@@ -298,6 +336,7 @@
         }))
 
 CPU_FREQ_TABLE = Table(
+    python_module=__file__,
     class_name='CpuFreqTable',
     sql_name='cpu_freq',
     columns=[
@@ -311,6 +350,7 @@
         }))
 
 CLOCK_SNAPSHOT_TABLE = Table(
+    python_module=__file__,
     class_name='ClockSnapshotTable',
     sql_name='clock_snapshot',
     columns=[
@@ -344,7 +384,15 @@
 
 # Keep this list sorted.
 ALL_TABLES = [
-    ARG_TABLE, CLOCK_SNAPSHOT_TABLE, CPU_FREQ_TABLE, CPU_TABLE,
-    EXP_MISSING_CHROME_PROC_TABLE, FILEDESCRIPTOR_TABLE, METADATA_TABLE,
-    PROCESS_TABLE, RAW_TABLE, THREAD_TABLE
+    ARG_TABLE,
+    CLOCK_SNAPSHOT_TABLE,
+    CPU_FREQ_TABLE,
+    CPU_TABLE,
+    EXP_MISSING_CHROME_PROC_TABLE,
+    FILEDESCRIPTOR_TABLE,
+    METADATA_TABLE,
+    PROCESS_TABLE,
+    RAW_TABLE,
+    THREAD_TABLE,
+    FTRACE_EVENT_TABLE,
 ]
diff --git a/src/trace_processor/tables/profiler_tables.h b/src/trace_processor/tables/profiler_tables.h
deleted file mode 100644
index f0e84b2..0000000
--- a/src/trace_processor/tables/profiler_tables.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_TABLES_PROFILER_TABLES_H_
-#define SRC_TRACE_PROCESSOR_TABLES_PROFILER_TABLES_H_
-
-#include "src/trace_processor/tables/macros.h"
-#include "src/trace_processor/tables/track_tables_py.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace tables {
-
-// A callsite. This is a list of frames that were on the stack.
-// This is generated by the stack profilers: heapprofd and traced_perf.
-// @param depth distance from the bottom-most frame of the callstack.
-// @param parent_id parent frame on the callstack. NULL for the bottom-most.
-// @param frame_id frame at this position in the callstack.
-// @tablegroup Callstack profilers
-#define PERFETTO_TP_STACK_PROFILE_CALLSITE_DEF(NAME, PARENT, C) \
-  NAME(StackProfileCallsiteTable, "stack_profile_callsite")     \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                             \
-  C(uint32_t, depth)                                            \
-  C(std::optional<StackProfileCallsiteTable::Id>, parent_id)    \
-  C(StackProfileFrameTable::Id, frame_id)
-
-}  // namespace tables
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_PROFILER_TABLES_H_
diff --git a/src/trace_processor/tables/profiler_tables.py b/src/trace_processor/tables/profiler_tables.py
index d428009..c712adff 100644
--- a/src/trace_processor/tables/profiler_tables.py
+++ b/src/trace_processor/tables/profiler_tables.py
@@ -28,6 +28,7 @@
 from src.trace_processor.tables.track_tables import TRACK_TABLE
 
 PROFILER_SMAPS_TABLE = Table(
+    python_module=__file__,
     class_name='ProfilerSmapsTable',
     sql_name='profiler_smaps',
     columns=[
@@ -94,6 +95,7 @@
         }))
 
 PACKAGE_LIST_TABLE = Table(
+    python_module=__file__,
     class_name='PackageListTable',
     sql_name='package_list',
     columns=[
@@ -123,6 +125,7 @@
         }))
 
 STACK_PROFILE_MAPPING_TABLE = Table(
+    python_module=__file__,
     class_name='StackProfileMappingTable',
     sql_name='stack_profile_mapping',
     columns=[
@@ -151,6 +154,7 @@
         }))
 
 STACK_PROFILE_FRAME_TABLE = Table(
+    python_module=__file__,
     class_name='StackProfileFrameTable',
     sql_name='stack_profile_frame',
     columns=[
@@ -181,6 +185,7 @@
         }))
 
 STACK_PROFILE_CALLSITE_TABLE = Table(
+    python_module=__file__,
     class_name='StackProfileCallsiteTable',
     sql_name='stack_profile_callsite',
     columns=[
@@ -204,6 +209,7 @@
         }))
 
 STACK_SAMPLE_TABLE = Table(
+    python_module=__file__,
     class_name='StackSampleTable',
     sql_name='stack_sample',
     columns=[
@@ -221,6 +227,7 @@
         }))
 
 CPU_PROFILE_STACK_SAMPLE_TABLE = Table(
+    python_module=__file__,
     class_name='CpuProfileStackSampleTable',
     sql_name='cpu_profile_stack_sample',
     columns=[
@@ -239,6 +246,7 @@
         }))
 
 PERF_SAMPLE_TABLE = Table(
+    python_module=__file__,
     class_name='PerfSampleTable',
     sql_name='perf_sample',
     columns=[
@@ -278,6 +286,7 @@
         }))
 
 SYMBOL_TABLE = Table(
+    python_module=__file__,
     class_name='SymbolTable',
     sql_name='stack_profile_symbol',
     columns=[
@@ -323,6 +332,7 @@
         }))
 
 HEAP_PROFILE_ALLOCATION_TABLE = Table(
+    python_module=__file__,
     class_name='HeapProfileAllocationTable',
     sql_name='heap_profile_allocation',
     columns=[
@@ -367,6 +377,7 @@
         }))
 
 EXPERIMENTAL_FLAMEGRAPH_NODES_TABLE = Table(
+    python_module=__file__,
     class_name='ExperimentalFlamegraphNodesTable',
     sql_name='experimental_flamegraph_nodes',
     columns=[
@@ -421,6 +432,7 @@
         }))
 
 HEAP_GRAPH_CLASS_TABLE = Table(
+    python_module=__file__,
     class_name='HeapGraphClassTable',
     sql_name='heap_graph_class',
     columns=[
@@ -455,6 +467,7 @@
         }))
 
 HEAP_GRAPH_OBJECT_TABLE = Table(
+    python_module=__file__,
     class_name='HeapGraphObjectTable',
     sql_name='heap_graph_object',
     columns=[
@@ -500,6 +513,7 @@
         }))
 
 HEAP_GRAPH_REFERENCE_TABLE = Table(
+    python_module=__file__,
     class_name='HeapGraphReferenceTable',
     sql_name='heap_graph_reference',
     columns=[
@@ -537,6 +551,7 @@
         }))
 
 VULKAN_MEMORY_ALLOCATIONS_TABLE = Table(
+    python_module=__file__,
     class_name='VulkanMemoryAllocationsTable',
     sql_name='vulkan_memory_allocations',
     columns=[
@@ -576,6 +591,7 @@
         }))
 
 GPU_COUNTER_GROUP_TABLE = Table(
+    python_module=__file__,
     class_name='GpuCounterGroupTable',
     sql_name='gpu_counter_group',
     columns=[
diff --git a/src/trace_processor/tables/macros_benchmark.cc b/src/trace_processor/tables/py_tables_benchmark.cc
similarity index 89%
rename from src/trace_processor/tables/macros_benchmark.cc
rename to src/trace_processor/tables/py_tables_benchmark.cc
index c946696..45a96e8 100644
--- a/src/trace_processor/tables/macros_benchmark.cc
+++ b/src/trace_processor/tables/py_tables_benchmark.cc
@@ -16,35 +16,16 @@
 
 #include <benchmark/benchmark.h>
 
-#include "src/trace_processor/tables/macros.h"
+#include "src/trace_processor/tables/py_tables_benchmark_py.h"
 
 namespace perfetto {
 namespace trace_processor {
-namespace {
-
-#define PERFETTO_TP_ROOT_TEST_TABLE(NAME, PARENT, C) \
-  NAME(RootTestTable, "root_table")                  \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                  \
-  C(uint32_t, root_sorted, Column::Flag::kSorted)    \
-  C(uint32_t, root_non_null)                         \
-  C(uint32_t, root_non_null_2)                       \
-  C(std::optional<uint32_t>, root_nullable)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_ROOT_TEST_TABLE);
-
-#define PERFETTO_TP_CHILD_TABLE(NAME, PARENT, C)   \
-  NAME(ChildTestTable, "child_table")              \
-  PARENT(PERFETTO_TP_ROOT_TEST_TABLE, C)           \
-  C(uint32_t, child_sorted, Column::Flag::kSorted) \
-  C(uint32_t, child_non_null)                      \
-  C(std::optional<uint32_t>, child_nullable)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_CHILD_TABLE);
+namespace tables {
 
 RootTestTable::~RootTestTable() = default;
 ChildTestTable::~ChildTestTable() = default;
 
-}  // namespace
+}  // namespace tables
 }  // namespace trace_processor
 }  // namespace perfetto
 
@@ -74,16 +55,16 @@
 
 }  // namespace
 
-using perfetto::trace_processor::ChildTestTable;
-using perfetto::trace_processor::RootTestTable;
 using perfetto::trace_processor::RowMap;
 using perfetto::trace_processor::SqlValue;
 using perfetto::trace_processor::StringPool;
 using perfetto::trace_processor::Table;
+using perfetto::trace_processor::tables::ChildTestTable;
+using perfetto::trace_processor::tables::RootTestTable;
 
 static void BM_TableInsert(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   for (auto _ : state) {
     benchmark::DoNotOptimize(root.Insert({}));
@@ -93,7 +74,7 @@
 
 static void BM_TableIteratorChild(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -116,7 +97,7 @@
 
 static void BM_TableFilterAndSortRoot(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
   uint32_t partitions = 8;
@@ -140,7 +121,7 @@
 
 static void BM_TableFilterRootId(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
   for (uint32_t i = 0; i < size; ++i)
@@ -154,7 +135,7 @@
 
 static void BM_TableFilterRootIdAndOther(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
 
@@ -173,7 +154,7 @@
 
 static void BM_TableFilterChildId(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -190,7 +171,7 @@
 
 static void BM_TableFilterChildIdAndSortedInRoot(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -213,7 +194,7 @@
 
 static void BM_TableFilterRootNonNullEqMatchMany(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
   uint32_t partitions = size / 1024;
@@ -232,7 +213,7 @@
 
 static void BM_TableFilterRootMultipleNonNull(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
   uint32_t partitions = size / 512;
@@ -254,7 +235,7 @@
 
 static void BM_TableFilterRootNullableEqMatchMany(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
   uint32_t partitions = size / 512;
@@ -277,7 +258,7 @@
 
 static void BM_TableFilterChildNonNullEqMatchMany(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -299,7 +280,7 @@
 
 static void BM_TableFilterChildNullableEqMatchMany(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -325,7 +306,7 @@
 static void BM_TableFilterChildNonNullEqMatchManyInParent(
     benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -349,7 +330,7 @@
 static void BM_TableFilterChildNullableEqMatchManyInParent(
     benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -372,7 +353,7 @@
 
 static void BM_TableFilterParentSortedEq(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
 
@@ -390,7 +371,7 @@
 
 static void BM_TableFilterParentSortedAndOther(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
 
@@ -415,7 +396,7 @@
 
 static void BM_TableFilterChildSortedEq(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -435,7 +416,7 @@
 
 static void BM_TableFilterChildSortedEqInParent(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -458,7 +439,7 @@
 
 static void BM_TableSortRootNonNull(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
 
@@ -479,7 +460,7 @@
 
 static void BM_TableSortRootNullable(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
 
@@ -501,7 +482,7 @@
 
 static void BM_TableSortChildNonNullInParent(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
@@ -529,7 +510,7 @@
 
 static void BM_TableSortChildNullableInParent(benchmark::State& state) {
   StringPool pool;
-  RootTestTable root(&pool, nullptr);
+  RootTestTable root(&pool);
   ChildTestTable child(&pool, &root);
 
   uint32_t size = static_cast<uint32_t>(state.range(0));
diff --git a/src/trace_processor/tables/py_tables_benchmark.py b/src/trace_processor/tables/py_tables_benchmark.py
new file mode 100644
index 0000000..8a2c1f9
--- /dev/null
+++ b/src/trace_processor/tables/py_tables_benchmark.py
@@ -0,0 +1,48 @@
+# 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.
+"""Contains tables for unittesting."""
+
+from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnFlag
+from python.generators.trace_processor_table.public import Table
+from python.generators.trace_processor_table.public import CppOptional
+from python.generators.trace_processor_table.public import CppUint32
+
+ROOT_TABLE = Table(
+    python_module=__file__,
+    class_name="RootTestTable",
+    sql_name="root_table",
+    columns=[
+        C("root_sorted", CppUint32(), flags=ColumnFlag.SORTED),
+        C("root_non_null", CppUint32()),
+        C("root_non_null_2", CppUint32()),
+        C("root_nullable", CppOptional(CppUint32())),
+    ])
+
+CHILD_TABLE = Table(
+    python_module=__file__,
+    class_name="ChildTestTable",
+    sql_name="child_table",
+    parent=ROOT_TABLE,
+    columns=[
+        C("child_sorted", CppUint32(), flags=ColumnFlag.SORTED),
+        C("child_non_null", CppUint32()),
+        C("child_nullable", CppOptional(CppUint32())),
+    ])
+
+# Keep this list sorted.
+ALL_TABLES = [
+    ROOT_TABLE,
+    CHILD_TABLE,
+]
diff --git a/src/trace_processor/tables/py_tables_unittest.cc b/src/trace_processor/tables/py_tables_unittest.cc
index 71b5f39..36facd6 100644
--- a/src/trace_processor/tables/py_tables_unittest.cc
+++ b/src/trace_processor/tables/py_tables_unittest.cc
@@ -15,6 +15,7 @@
  */
 
 #include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/column_storage.h"
 #include "src/trace_processor/tables/py_tables_unittest_py.h"
 
 #include "test/gtest_and_gmock.h"
@@ -24,6 +25,7 @@
 namespace tables {
 
 TestEventTable::~TestEventTable() = default;
+TestEventChildTable::~TestEventChildTable() = default;
 TestSliceTable::~TestSliceTable() = default;
 TestArgsTable::~TestArgsTable() = default;
 
@@ -34,6 +36,7 @@
   StringPool pool_;
 
   TestEventTable event_{&pool_};
+  TestEventChildTable event_child_{&pool_, &event_};
   TestSliceTable slice_{&pool_, &event_};
   TestArgsTable args_{&pool_};
 };
@@ -163,6 +166,150 @@
   ASSERT_EQ(slice_.dur()[1], 20);
 }
 
+TEST_F(PyTablesUnittest, Extend) {
+  event_.Insert(TestEventTable::Row(50, 0));
+  event_.Insert(TestEventTable::Row(100, 1));
+  event_.Insert(TestEventTable::Row(150, 2));
+
+  ColumnStorage<int64_t> dur;
+  dur.Append(512);
+  dur.Append(1024);
+  dur.Append(2048);
+
+  auto slice_ext = TestSliceTable::ExtendParent(event_, std::move(dur));
+  ASSERT_EQ(slice_ext->row_count(), 3u);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(0).AsLong(),
+      50);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(0).AsLong(),
+      512);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(1).AsLong(),
+      100);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(1).AsLong(),
+      1024);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(2).AsLong(),
+      150);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(2).AsLong(),
+      2048);
+}
+
+TEST_F(PyTablesUnittest, SelectAndExtend) {
+  event_.Insert(TestEventTable::Row(50, 0));
+  event_.Insert(TestEventTable::Row(100, 1));
+  event_.Insert(TestEventTable::Row(150, 2));
+
+  std::vector<TestEventTable::RowNumber> rows;
+  rows.emplace_back(TestEventTable::RowNumber(1));
+  ColumnStorage<int64_t> dur;
+  dur.Append(1024);
+
+  auto slice_ext = TestSliceTable::SelectAndExtendParent(
+      event_, std::move(rows), std::move(dur));
+  ASSERT_EQ(slice_ext->row_count(), 1u);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(0).AsLong(),
+      100);
+  ASSERT_EQ(
+      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(0).AsLong(),
+      1024);
+}
+
+TEST_F(PyTablesUnittest, SetIdColumns) {
+  StringPool pool;
+  TestArgsTable table{&pool};
+
+  table.Insert(TestArgsTable::Row(0, 100));
+  table.Insert(TestArgsTable::Row(0, 200));
+  table.Insert(TestArgsTable::Row(2, 200));
+  table.Insert(TestArgsTable::Row(3, 300));
+  table.Insert(TestArgsTable::Row(4, 200));
+  table.Insert(TestArgsTable::Row(4, 500));
+  table.Insert(TestArgsTable::Row(4, 900));
+  table.Insert(TestArgsTable::Row(4, 200));
+  table.Insert(TestArgsTable::Row(8, 400));
+
+  ASSERT_EQ(table.row_count(), 9u);
+  ASSERT_TRUE(table.arg_set_id().IsSetId());
+
+  // Verify that not-present ids are not returned.
+  {
+    static constexpr uint32_t kFilterArgSetId = 1;
+    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
+    ASSERT_EQ(res.row_count(), 0u);
+  }
+  {
+    static constexpr uint32_t kFilterArgSetId = 9;
+    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
+    ASSERT_EQ(res.row_count(), 0u);
+  }
+
+  // Verify that kSetId flag is correctly removed after filtering/sorting.
+  {
+    static constexpr uint32_t kFilterArgSetId = 3;
+    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
+    ASSERT_EQ(res.row_count(), 1u);
+    ASSERT_FALSE(res.GetColumnByName("arg_set_id")->IsSetId());
+  }
+  {
+    auto res = table.Sort({table.type().descending()});
+    ASSERT_FALSE(res.GetColumnByName("arg_set_id")->IsSetId());
+  }
+
+  uint32_t arg_set_id_col_idx =
+      static_cast<uint32_t>(TestArgsTable::ColumnIndex::arg_set_id);
+
+  // Verify that filtering equality for real arg set ids works as expected.
+  {
+    static constexpr uint32_t kFilterArgSetId = 4;
+    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
+    ASSERT_EQ(res.row_count(), 4u);
+    for (auto it = res.IterateRows(); it; it.Next()) {
+      uint32_t arg_set_id =
+          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
+      ASSERT_EQ(arg_set_id, kFilterArgSetId);
+    }
+  }
+  {
+    static constexpr uint32_t kFilterArgSetId = 0;
+    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
+    ASSERT_EQ(res.row_count(), 2u);
+    for (auto it = res.IterateRows(); it; it.Next()) {
+      uint32_t arg_set_id =
+          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
+      ASSERT_EQ(arg_set_id, kFilterArgSetId);
+    }
+  }
+  {
+    static constexpr uint32_t kFilterArgSetId = 8;
+    auto res = table.Filter({table.arg_set_id().eq(kFilterArgSetId)});
+    ASSERT_EQ(res.row_count(), 1u);
+    for (auto it = res.IterateRows(); it; it.Next()) {
+      uint32_t arg_set_id =
+          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
+      ASSERT_EQ(arg_set_id, kFilterArgSetId);
+    }
+  }
+
+  // Verify that filtering equality for arg set ids after filtering another
+  // column works.
+  {
+    static constexpr uint32_t kFilterArgSetId = 4;
+    auto res = table.Filter(
+        {table.int_value().eq(200), table.arg_set_id().eq(kFilterArgSetId)});
+    ASSERT_EQ(res.row_count(), 2u);
+    for (auto it = res.IterateRows(); it; it.Next()) {
+      uint32_t arg_set_id =
+          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
+      ASSERT_EQ(arg_set_id, kFilterArgSetId);
+    }
+  }
+}
+
 }  // namespace
 }  // namespace tables
 }  // namespace trace_processor
diff --git a/src/trace_processor/tables/py_tables_unittest.py b/src/trace_processor/tables/py_tables_unittest.py
index 24cb17c..8acc688 100644
--- a/src/trace_processor/tables/py_tables_unittest.py
+++ b/src/trace_processor/tables/py_tables_unittest.py
@@ -17,40 +17,48 @@
 from python.generators.trace_processor_table.public import ColumnFlag
 from python.generators.trace_processor_table.public import CppInt64
 from python.generators.trace_processor_table.public import Table
-from python.generators.trace_processor_table.public import TableDoc
 from python.generators.trace_processor_table.public import CppUint32
 
 EVENT_TABLE = Table(
+    python_module=__file__,
     class_name="TestEventTable",
     sql_name="event",
     columns=[
         C("ts", CppInt64(), flags=ColumnFlag.SORTED),
         C("arg_set_id", CppUint32()),
-    ],
-    tabledoc=TableDoc(doc='', group='', columns={}))
+    ])
+
+EVENT_CHILD_TABLE = Table(
+    python_module=__file__,
+    class_name="TestEventChildTable",
+    sql_name="event",
+    parent=EVENT_TABLE,
+    columns=[])
 
 SLICE_TABLE = Table(
+    python_module=__file__,
     class_name="TestSliceTable",
     sql_name="slice",
     parent=EVENT_TABLE,
     columns=[
         C("dur", CppInt64()),
-    ],
-    tabledoc=TableDoc(doc='', group='', columns={}))
+    ])
 
 ARGS_TABLE = Table(
+    python_module=__file__,
     class_name="TestArgsTable",
     sql_name="args",
     columns=[
         C("arg_set_id",
           CppUint32(),
           flags=ColumnFlag.SET_ID | ColumnFlag.SORTED),
-    ],
-    tabledoc=TableDoc(doc='', group='', columns={}))
+        C("int_value", CppInt64()),
+    ])
 
 # Keep this list sorted.
 ALL_TABLES = [
     ARGS_TABLE,
     EVENT_TABLE,
+    EVENT_CHILD_TABLE,
     SLICE_TABLE,
 ]
diff --git a/src/trace_processor/tables/slice_tables.h b/src/trace_processor/tables/slice_tables.h
deleted file mode 100644
index e33ae19..0000000
--- a/src/trace_processor/tables/slice_tables.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_TABLES_SLICE_TABLES_H_
-#define SRC_TRACE_PROCESSOR_TABLES_SLICE_TABLES_H_
-
-#include "src/trace_processor/tables/macros.h"
-#include "src/trace_processor/tables/track_tables_py.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace tables {
-
-#define PERFETTO_TP_SLICE_TABLE_DEF(NAME, PARENT, C)  \
-  NAME(SliceTable, "internal_slice")                  \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                   \
-  C(int64_t, ts, Column::Flag::kSorted)               \
-  C(int64_t, dur)                                     \
-  C(TrackTable::Id, track_id)                         \
-  C(std::optional<StringPool::Id>, category)          \
-  C(std::optional<StringPool::Id>, name)              \
-  C(uint32_t, depth)                                  \
-  C(int64_t, stack_id)                                \
-  C(int64_t, parent_stack_id)                         \
-  C(std::optional<SliceTable::Id>, parent_id)         \
-  C(uint32_t, arg_set_id)                             \
-  C(std::optional<int64_t>, thread_ts)                \
-  C(std::optional<int64_t>, thread_dur)               \
-  C(std::optional<int64_t>, thread_instruction_count) \
-  C(std::optional<int64_t>, thread_instruction_delta)
-
-#define PERFETTO_TP_SCHED_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(SchedSliceTable, "sched_slice")                     \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                        \
-  C(int64_t, ts, Column::Flag::kSorted)                    \
-  C(int64_t, dur)                                          \
-  C(uint32_t, cpu)                                         \
-  C(uint32_t, utid)                                        \
-  C(StringPool::Id, end_state)                             \
-  C(int32_t, priority)
-
-}  // namespace tables
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_SLICE_TABLES_H_
diff --git a/src/trace_processor/tables/slice_tables.py b/src/trace_processor/tables/slice_tables.py
index 50b8c3e..f58d53d 100644
--- a/src/trace_processor/tables/slice_tables.py
+++ b/src/trace_processor/tables/slice_tables.py
@@ -11,9 +11,10 @@
 # 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.
-"""Contains tables for relevant for TODO."""
+"""Contains tables for relevant for slices."""
 
 from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnDoc
 from python.generators.trace_processor_table.public import ColumnFlag
 from python.generators.trace_processor_table.public import CppInt32
 from python.generators.trace_processor_table.public import CppInt64
@@ -29,6 +30,7 @@
 from src.trace_processor.tables.track_tables import TRACK_TABLE
 
 SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='SliceTable',
     sql_name='internal_slice',
     columns=[
@@ -49,26 +51,77 @@
     ],
     wrapping_sql_view=WrappingSqlView('slice'),
     tabledoc=TableDoc(
-        doc='''''',
+        doc='''
+          Contains slices from userspace which explains what threads were doing
+          during the trace.
+        ''',
         group='Events',
         columns={
-            'ts': '''timestamp of the start of the slice (in nanoseconds)''',
-            'dur': '''duration of the slice (in nanoseconds)''',
-            'arg_set_id': '''''',
-            'thread_instruction_count': '''to the end of the slice.''',
-            'thread_instruction_delta': '''The change in value from''',
-            'track_id': '''''',
-            'category': '''''',
-            'name': '''''',
-            'depth': '''''',
-            'stack_id': '''''',
-            'parent_stack_id': '''''',
-            'parent_id': '''''',
-            'thread_ts': '''''',
-            'thread_dur': ''''''
+            'ts':
+                'The timestamp at the start of the slice (in nanoseconds).',
+            'dur':
+                'The duration of the slice (in nanoseconds).',
+            'track_id':
+                'The id of the track this slice is located on.',
+            'category':
+                '''
+                  The "category" of the slice. If this slice originated with
+                  track_event, this column contains the category emitted.
+                  Otherwise, it is likely to be null (with limited exceptions).
+                ''',
+            'name':
+                '''
+                  The name of the slice. The name describes what was happening
+                  during the slice.
+                ''',
+            'depth':
+                'The depth of the slice in the current stack of slices.',
+            'stack_id':
+                '''
+                  A unique identifier obtained from the names of all slices
+                  in this stack. This is rarely useful and kept around only
+                  for legacy reasons.
+                ''',
+            'parent_stack_id':
+                'The stack_id for the parent of this slice. Rarely useful.',
+            'parent_id':
+                '''
+                  The id of the parent (i.e. immediate ancestor) slice for this
+                  slice
+                ''',
+            'arg_set_id':
+                ColumnDoc(
+                    'The id of the argument set associated with this slice',
+                    joinable='args.arg_set_id'),
+            'thread_ts':
+                '''
+                  The thread timestamp at the start of the slice. This column
+                  will only be populated if thread timestamp collection is
+                  enabled with track_event.
+                ''',
+            'thread_dur':
+                ''''
+                  The thread time used by this slice. This column will only be
+                  populated if thread timestamp collection is enabled with
+                  track_event.
+                ''',
+            'thread_instruction_count':
+                '''
+                  The value of the CPU instruction counter at the start of the
+                  slice. This column will only be populated if thread
+                  instruction collection is enabled with track_event.
+                ''',
+            'thread_instruction_delta':
+                '''
+                  The change in value of the CPU instruction counter between the
+                  start and end of the slice. This column will only be
+                  populated if thread instruction collection is enabled with
+                  track_event.
+                ''',
         }))
 
 SCHED_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='SchedSliceTable',
     sql_name='sched_slice',
     columns=[
@@ -82,8 +135,8 @@
     tabledoc=TableDoc(
         doc='''
           This table holds slices with kernel thread scheduling information.
-These slices are collected when the Linux "ftrace" data source is
-used with the "sched/switch" and "sched/wakeup*" events enabled.
+          These slices are collected when the Linux "ftrace" data source is
+          used with the "sched/switch" and "sched/wakeup*" events enabled.
         ''',
         group='Events',
         columns={
@@ -96,45 +149,69 @@
             'cpu':
                 '''The CPU that the slice executed on.''',
             'end_state':
-                '''A string representing the scheduling state of the
-kernel thread at the end of the slice.  The individual characters in
-the string mean the following: R (runnable), S (awaiting a wakeup),
-D (in an uninterruptible sleep), T (suspended), t (being traced),
-X (exiting), P (parked), W (waking), I (idle), N (not contributing
-to the load average), K (wakeable on fatal signals) and
-Z (zombie, awaiting cleanup).''',
+                '''
+                  A string representing the scheduling state of the kernel
+                  thread at the end of the slice.  The individual characters in
+                  the string mean the following: R (runnable), S (awaiting a
+                  wakeup), D (in an uninterruptible sleep), T (suspended),
+                  t (being traced), X (exiting), P (parked), W (waking),
+                  I (idle), N (not contributing to the load average),
+                  K (wakeable on fatal signals) and Z (zombie, awaiting
+                  cleanup).
+                ''',
             'priority':
                 '''The kernel priority that the thread ran at.'''
         }))
 
 THREAD_STATE_TABLE = Table(
+    python_module=__file__,
     class_name='ThreadStateTable',
     sql_name='thread_state',
     columns=[
-        C('utid', CppUint32()),
         C('ts', CppInt64()),
         C('dur', CppInt64()),
         C('cpu', CppOptional(CppUint32())),
+        C('utid', CppUint32()),
         C('state', CppString()),
         C('io_wait', CppOptional(CppUint32())),
         C('blocked_function', CppOptional(CppString())),
         C('waker_utid', CppOptional(CppUint32())),
     ],
     tabledoc=TableDoc(
-        doc='''''',
+        doc='''
+          This table contains the scheduling state of every thread on the
+          system during the trace. It is a subset of the |sched_slice| (sched)
+          table which only contains the times where threads were actually
+          scheduled.
+        ''',
         group='Events',
         columns={
-            'utid': '''''',
-            'ts': '''''',
-            'dur': '''''',
-            'cpu': '''''',
-            'state': '''''',
-            'io_wait': '''''',
-            'blocked_function': '''''',
-            'waker_utid': ''''''
+            'ts':
+                'The timestamp at the start of the slice (in nanoseconds).',
+            'dur':
+                'The duration of the slice (in nanoseconds).',
+            'cpu':
+                '''The CPU that the slice executed on.''',
+            'utid':
+                '''The thread's unique id in the trace..''',
+            'state':
+                '''
+                  The scheduling state of the thread. Can be "Running" or any
+                  of the states described in |sched_slice.end_state|.
+                ''',
+            'io_wait':
+                'Indicates whether this thread was blocked on IO.',
+            'blocked_function':
+                'The function in the kernel this thread was blocked on.',
+            'waker_utid':
+                '''
+                  The unique thread id of the thread which caused a wakeup of
+                  this thread.
+                '''
         }))
 
 GPU_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='GpuSliceTable',
     sql_name='gpu_slice',
     columns=[
@@ -169,6 +246,7 @@
         }))
 
 GRAPHICS_FRAME_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='GraphicsFrameSliceTable',
     sql_name='frame_slice',
     columns=[
@@ -191,6 +269,7 @@
         }))
 
 EXPECTED_FRAME_TIMELINE_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='ExpectedFrameTimelineSliceTable',
     sql_name='expected_frame_timeline_slice',
     columns=[
@@ -202,7 +281,7 @@
     parent=SLICE_TABLE,
     tabledoc=TableDoc(
         doc='''''',
-        group='Misc',
+        group='Events',
         columns={
             'display_frame_token': '''''',
             'surface_frame_token': '''''',
@@ -211,6 +290,7 @@
         }))
 
 ACTUAL_FRAME_TIMELINE_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='ActualFrameTimelineSliceTable',
     sql_name='actual_frame_timeline_slice',
     columns=[
@@ -228,7 +308,7 @@
     parent=SLICE_TABLE,
     tabledoc=TableDoc(
         doc='''''',
-        group='Misc',
+        group='Events',
         columns={
             'display_frame_token': '''''',
             'surface_frame_token': '''''',
@@ -243,6 +323,7 @@
         }))
 
 EXPERIMENTAL_FLAT_SLICE_TABLE = Table(
+    python_module=__file__,
     class_name='ExperimentalFlatSliceTable',
     sql_name='experimental_flat_slice',
     columns=[
@@ -257,18 +338,35 @@
         C('end_bound', CppInt64(), flags=ColumnFlag.HIDDEN),
     ],
     tabledoc=TableDoc(
-        doc='''''',
+        doc='''
+          An experimental table which "flattens" stacks of slices to contain
+          only the "deepest" slice at any point in time on each track.
+        ''',
         group='Misc',
         columns={
-            'ts': '''''',
-            'dur': '''''',
-            'track_id': '''''',
-            'category': '''''',
-            'name': '''''',
-            'arg_set_id': '''''',
-            'source_id': '''''',
-            'start_bound': '''''',
-            'end_bound': ''''''
+            'ts':
+                '''The timestamp at the start of the slice (in nanoseconds).''',
+            'dur':
+                '''The duration of the slice (in nanoseconds).''',
+            'track_id':
+                'The id of the track this slice is located on.',
+            'category':
+                '''
+                  The "category" of the slice. If this slice originated with
+                  track_event, this column contains the category emitted.
+                  Otherwise, it is likely to be null (with limited exceptions).
+                ''',
+            'name':
+                '''
+                  The name of the slice. The name describes what was happening
+                  during the slice.
+                ''',
+            'arg_set_id':
+                ColumnDoc(
+                    'The id of the argument set associated with this slice',
+                    joinable='args.arg_set_id'),
+            'source_id':
+                'The id of the slice which this row originated from.',
         }))
 
 # Keep this list sorted.
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 5025ab3..3dcf113 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -42,6 +42,7 @@
 
 // metadata_tables_py.h
 RawTable::~RawTable() = default;
+FtraceEventTable::~FtraceEventTable() = default;
 ArgTable::~ArgTable() = default;
 ExpMissingChromeProcTable::~ExpMissingChromeProcTable() = default;
 MetadataTable::~MetadataTable() = default;
diff --git a/src/trace_processor/tables/trace_proto_tables.h b/src/trace_processor/tables/trace_proto_tables.h
deleted file mode 100644
index aa57b59..0000000
--- a/src/trace_processor/tables/trace_proto_tables.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef SRC_TRACE_PROCESSOR_TABLES_TRACE_PROTO_TABLES_H_
-#define SRC_TRACE_PROCESSOR_TABLES_TRACE_PROTO_TABLES_H_
-
-#include "src/trace_processor/tables/macros.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace tables {
-
-// Experimental table, subject to arbitrary breaking changes.
-#define PERFETTO_TP_EXPERIMENTAL_PROTO_PATH_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ExperimentalProtoPathTable, "experimental_proto_path")          \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                                    \
-  C(std::optional<ExperimentalProtoPathTable::Id>, parent_id)          \
-  C(StringPool::Id, field_type)                                        \
-  C(std::optional<StringPool::Id>, field_name)                         \
-  C(std::optional<uint32_t>, arg_set_id)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_EXPERIMENTAL_PROTO_PATH_TABLE_DEF);
-
-#define PERFETTO_TP_EXPERIMENTAL_PROTO_CONTENT_TABLE_DEF(NAME, PARENT, C) \
-  NAME(ExperimentalProtoContentTable, "experimental_proto_content")       \
-  PERFETTO_TP_ROOT_TABLE(PARENT, C)                                       \
-  C(StringPool::Id, path)                                                 \
-  C(ExperimentalProtoPathTable::Id, path_id)                              \
-  C(int64_t, total_size)                                                  \
-  C(int64_t, size)                                                        \
-  C(int64_t, count)
-
-PERFETTO_TP_TABLE(PERFETTO_TP_EXPERIMENTAL_PROTO_CONTENT_TABLE_DEF);
-
-}  // namespace tables
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_TABLES_TRACE_PROTO_TABLES_H_
diff --git a/src/trace_processor/tables/trace_proto_tables.py b/src/trace_processor/tables/trace_proto_tables.py
index f6f5f9d..e9c9754 100644
--- a/src/trace_processor/tables/trace_proto_tables.py
+++ b/src/trace_processor/tables/trace_proto_tables.py
@@ -24,6 +24,7 @@
 from python.generators.trace_processor_table.public import CppUint32
 
 EXPERIMENTAL_PROTO_PATH_TABLE = Table(
+    python_module=__file__,
     class_name='ExperimentalProtoPathTable',
     sql_name='experimental_proto_path',
     columns=[
@@ -45,6 +46,7 @@
         }))
 
 EXPERIMENTAL_PROTO_CONTENT_TABLE = Table(
+    python_module=__file__,
     class_name='ExperimentalProtoContentTable',
     sql_name='experimental_proto_content',
     columns=[
diff --git a/src/trace_processor/tables/track_tables.py b/src/trace_processor/tables/track_tables.py
index bb5a0df..3cbce76 100644
--- a/src/trace_processor/tables/track_tables.py
+++ b/src/trace_processor/tables/track_tables.py
@@ -25,6 +25,7 @@
 from python.generators.trace_processor_table.public import CppUint32
 
 TRACK_TABLE = Table(
+    python_module=__file__,
     class_name="TrackTable",
     sql_name="track",
     columns=[
@@ -62,6 +63,7 @@
         }))
 
 PROCESS_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name="ProcessTrackTable",
     sql_name="process_track",
     columns=[
@@ -81,6 +83,7 @@
         }))
 
 THREAD_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='ThreadTrackTable',
     sql_name='thread_track',
     columns=[
@@ -101,6 +104,7 @@
         }))
 
 CPU_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='CpuTrackTable',
     sql_name='cpu_track',
     columns=[
@@ -113,6 +117,7 @@
         columns={'cpu': 'The CPU associated with this track'}))
 
 GPU_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='GpuTrackTable',
     sql_name='gpu_track',
     columns=[
@@ -134,6 +139,7 @@
         }))
 
 COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='CounterTrackTable',
     sql_name='counter_track',
     columns=[
@@ -156,6 +162,7 @@
         }))
 
 THREAD_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='ThreadCounterTrackTable',
     sql_name='thread_counter_track',
     columns=[
@@ -174,6 +181,7 @@
         }))
 
 PROCESS_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='ProcessCounterTrackTable',
     sql_name='process_counter_track',
     columns=[
@@ -193,6 +201,7 @@
         }))
 
 CPU_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='CpuCounterTrackTable',
     sql_name='cpu_counter_track',
     columns=[
@@ -205,6 +214,7 @@
         columns={'cpu': 'The CPU this track is associated with'}))
 
 IRQ_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='IrqCounterTrackTable',
     sql_name='irq_counter_track',
     columns=[
@@ -217,6 +227,7 @@
         columns={'irq': 'The identifier for the hardirq.'}))
 
 SOFTIRQ_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='SoftirqCounterTrackTable',
     sql_name='softirq_counter_track',
     columns=[
@@ -229,6 +240,7 @@
         columns={'softirq': 'The identifier for the softirq.'}))
 
 GPU_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='GpuCounterTrackTable',
     sql_name='gpu_counter_track',
     columns=[
@@ -241,6 +253,7 @@
         columns={'gpu_id': 'The identifier for the GPU.'}))
 
 PERF_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='PerfCounterTrackTable',
     sql_name='perf_counter_track',
     columns=[
@@ -265,6 +278,7 @@
         }))
 
 ENERGY_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='EnergyCounterTrackTable',
     sql_name='energy_counter_track',
     columns=[
@@ -286,6 +300,7 @@
         }))
 
 UID_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='UidCounterTrackTable',
     sql_name='uid_counter_track',
     columns=[
@@ -298,6 +313,7 @@
         columns={'uid': 'uid of process for which breakdowns are emitted'}))
 
 ENERGY_PER_UID_COUNTER_TRACK_TABLE = Table(
+    python_module=__file__,
     class_name='EnergyPerUidCounterTrackTable',
     sql_name='energy_per_uid_counter_track',
     columns=[
diff --git a/src/trace_processor/tp_metatrace.cc b/src/trace_processor/tp_metatrace.cc
index 79e6dbc..bf44dcf 100644
--- a/src/trace_processor/tp_metatrace.cc
+++ b/src/trace_processor/tp_metatrace.cc
@@ -24,19 +24,14 @@
 
 using ProtoEnum = protos::pbzero::MetatraceCategories;
 ProtoEnum MetatraceCategoriesToProtoEnum(MetatraceCategories categories) {
-  switch (categories) {
-    case MetatraceCategories::TOPLEVEL:
-      return ProtoEnum::TOPLEVEL;
-    case MetatraceCategories::FUNCTION:
-      return ProtoEnum::FUNCTION;
-    case MetatraceCategories::QUERY:
-      return ProtoEnum::QUERY;
-    case MetatraceCategories::ALL:
-      return ProtoEnum::ALL;
-    case MetatraceCategories::NONE:
-      return ProtoEnum::NONE;
-  }
-  return ProtoEnum::NONE;
+  ProtoEnum result = ProtoEnum::NONE;
+  if (categories & MetatraceCategories::TOPLEVEL)
+    result = static_cast<ProtoEnum>(result | ProtoEnum::TOPLEVEL);
+  if (categories & MetatraceCategories::FUNCTION)
+    result = static_cast<ProtoEnum>(result | ProtoEnum::FUNCTION);
+  if (categories & MetatraceCategories::QUERY)
+    result = static_cast<ProtoEnum>(result | ProtoEnum::QUERY);
+  return result;
 }
 
 }  // namespace
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index ee3f5d9..f64b1c1 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -53,9 +53,10 @@
 #include "src/trace_processor/prelude/functions/import.h"
 #include "src/trace_processor/prelude/functions/layout_functions.h"
 #include "src/trace_processor/prelude/functions/pprof_functions.h"
-#include "src/trace_processor/prelude/functions/register_function.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
 #include "src/trace_processor/prelude/functions/sqlite3_str_split.h"
 #include "src/trace_processor/prelude/functions/stack_functions.h"
+#include "src/trace_processor/prelude/functions/to_ftrace.h"
 #include "src/trace_processor/prelude/functions/utils.h"
 #include "src/trace_processor/prelude/functions/window_functions.h"
 #include "src/trace_processor/prelude/operators/span_join_operator.h"
@@ -71,9 +72,9 @@
 #include "src/trace_processor/prelude/table_functions/experimental_slice_layout.h"
 #include "src/trace_processor/prelude/table_functions/table_function.h"
 #include "src/trace_processor/prelude/table_functions/view.h"
+#include "src/trace_processor/prelude/tables_views/tables_views.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_stats_table.h"
-#include "src/trace_processor/sqlite/sqlite_raw_table.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 #include "src/trace_processor/sqlite/stats_table.h"
@@ -96,15 +97,6 @@
 #include "src/trace_processor/metrics/sql/amalgamated_sql_metrics.h"
 #include "src/trace_processor/stdlib/amalgamated_stdlib.h"
 
-// In Android and Chromium tree builds, we don't have the percentile module.
-// Just don't include it.
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
-// defined in sqlite_src/ext/misc/percentile.c
-extern "C" int sqlite3_percentile_init(sqlite3* db,
-                                       char** error,
-                                       const sqlite3_api_routines* api);
-#endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
-
 namespace perfetto {
 namespace trace_processor {
 namespace {
@@ -114,35 +106,17 @@
     "* FROM sqlite_temp_master)";
 
 template <typename SqlFunction, typename Ptr = typename SqlFunction::Context*>
-void RegisterFunction(sqlite3* db,
+void RegisterFunction(SqliteEngine* engine,
                       const char* name,
                       int argc,
                       Ptr context = nullptr,
                       bool deterministic = true) {
-  auto status = RegisterSqlFunction<SqlFunction>(
-      db, name, argc, std::move(context), deterministic);
+  auto status = engine->RegisterSqlFunction<SqlFunction>(
+      name, argc, std::move(context), deterministic);
   if (!status.ok())
     PERFETTO_ELOG("%s", status.c_message());
 }
 
-void InitializeSqlite(sqlite3* db) {
-  char* error = nullptr;
-  sqlite3_exec(db, "PRAGMA temp_store=2", nullptr, nullptr, &error);
-  if (error) {
-    PERFETTO_FATAL("Error setting pragma temp_store: %s", error);
-  }
-  sqlite3_str_split_init(db);
-// In Android tree builds, we don't have the percentile module.
-// Just don't include it.
-#if PERFETTO_BUILDFLAG(PERFETTO_TP_PERCENTILE)
-  sqlite3_percentile_init(db, &error, nullptr);
-  if (error) {
-    PERFETTO_ELOG("Error initializing: %s", error);
-    sqlite3_free(error);
-  }
-#endif
-}
-
 void BuildBoundsTable(sqlite3* db, std::pair<int64_t, int64_t> bounds) {
   char* error = nullptr;
   sqlite3_exec(db, "DELETE FROM trace_bounds", nullptr, nullptr, &error);
@@ -152,167 +126,16 @@
     return;
   }
 
-  char* insert_sql = sqlite3_mprintf("INSERT INTO trace_bounds VALUES(%" PRId64
-                                     ", %" PRId64 ")",
-                                     bounds.first, bounds.second);
-
-  sqlite3_exec(db, insert_sql, nullptr, nullptr, &error);
-  sqlite3_free(insert_sql);
+  base::StackString<1024> sql("INSERT INTO trace_bounds VALUES(%" PRId64
+                              ", %" PRId64 ")",
+                              bounds.first, bounds.second);
+  sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &error);
   if (error) {
     PERFETTO_ELOG("Error inserting bounds table: %s", error);
     sqlite3_free(error);
   }
 }
 
-void CreateBuiltinTables(sqlite3* db) {
-  char* error = nullptr;
-  sqlite3_exec(db, "CREATE TABLE perfetto_tables(name STRING)", nullptr,
-               nullptr, &error);
-  if (error) {
-    PERFETTO_ELOG("Error initializing: %s", error);
-    sqlite3_free(error);
-  }
-  sqlite3_exec(db, "CREATE TABLE trace_bounds(start_ts BIGINT, end_ts BIGINT)",
-               nullptr, nullptr, &error);
-  if (error) {
-    PERFETTO_ELOG("Error initializing: %s", error);
-    sqlite3_free(error);
-  }
-  // Ensure that the entries in power_profile are unique to prevent duplicates
-  // when the power_profile is augmented with additional profiles.
-  sqlite3_exec(db,
-               "CREATE TABLE power_profile("
-               "device STRING, cpu INT, cluster INT, freq INT, power DOUBLE,"
-               "UNIQUE(device, cpu, cluster, freq));",
-               nullptr, nullptr, &error);
-  if (error) {
-    PERFETTO_ELOG("Error initializing: %s", error);
-    sqlite3_free(error);
-  }
-  sqlite3_exec(db, "CREATE TABLE trace_metrics(name STRING)", nullptr, nullptr,
-               &error);
-  if (error) {
-    PERFETTO_ELOG("Error initializing: %s", error);
-    sqlite3_free(error);
-  }
-  // This is a table intended to be used for metric debugging/developing. Data
-  // in the table is shown specially in the UI, and users can insert rows into
-  // this table to draw more things.
-  sqlite3_exec(db,
-               "CREATE TABLE debug_slices (id BIGINT, name STRING, ts BIGINT,"
-               "dur BIGINT, depth BIGINT)",
-               nullptr, nullptr, &error);
-  if (error) {
-    PERFETTO_ELOG("Error initializing: %s", error);
-    sqlite3_free(error);
-  }
-
-  // Initialize the bounds table with some data so even before parsing any data,
-  // we still have a valid table.
-  BuildBoundsTable(db, std::make_pair(0, 0));
-}
-
-void MaybeRegisterError(char* error) {
-  if (error) {
-    PERFETTO_ELOG("Error initializing: %s", error);
-    sqlite3_free(error);
-  }
-}
-
-void CreateBuiltinViews(sqlite3* db) {
-  char* error = nullptr;
-  sqlite3_exec(db,
-               "CREATE VIEW counters AS "
-               "SELECT * "
-               "FROM counter v "
-               "JOIN counter_track t "
-               "ON v.track_id = t.id "
-               "ORDER BY ts;",
-               nullptr, nullptr, &error);
-  MaybeRegisterError(error);
-
-  sqlite3_exec(db,
-               "CREATE VIEW slice AS "
-               "SELECT "
-               "  *, "
-               "  category AS cat, "
-               "  id AS slice_id "
-               "FROM internal_slice;",
-               nullptr, nullptr, &error);
-  MaybeRegisterError(error);
-
-  sqlite3_exec(db,
-               "CREATE VIEW instant AS "
-               "SELECT "
-               "ts, track_id, name, arg_set_id "
-               "FROM slice "
-               "WHERE dur = 0;",
-               nullptr, nullptr, &error);
-  MaybeRegisterError(error);
-
-  sqlite3_exec(db,
-               "CREATE VIEW sched AS "
-               "SELECT "
-               "*, "
-               "ts + dur as ts_end "
-               "FROM sched_slice;",
-               nullptr, nullptr, &error);
-  MaybeRegisterError(error);
-
-  sqlite3_exec(db,
-               "CREATE VIEW slices AS "
-               "SELECT * FROM slice;",
-               nullptr, nullptr, &error);
-  MaybeRegisterError(error);
-
-  sqlite3_exec(db,
-               "CREATE VIEW thread AS "
-               "SELECT "
-               "id as utid, "
-               "* "
-               "FROM internal_thread;",
-               nullptr, nullptr, &error);
-  MaybeRegisterError(error);
-
-  sqlite3_exec(db,
-               "CREATE VIEW process AS "
-               "SELECT "
-               "id as upid, "
-               "* "
-               "FROM internal_process;",
-               nullptr, nullptr, &error);
-  MaybeRegisterError(error);
-
-  // This should be kept in sync with GlobalArgsTracker::AddArgSet.
-  sqlite3_exec(db,
-               "CREATE VIEW args AS "
-               "SELECT "
-               "*, "
-               "CASE value_type "
-               "  WHEN 'int' THEN CAST(int_value AS text) "
-               "  WHEN 'uint' THEN CAST(int_value AS text) "
-               "  WHEN 'string' THEN string_value "
-               "  WHEN 'real' THEN CAST(real_value AS text) "
-               "  WHEN 'pointer' THEN printf('0x%x', int_value) "
-               "  WHEN 'bool' THEN ( "
-               "    CASE WHEN int_value <> 0 THEN 'true' "
-               "    ELSE 'false' END) "
-               "  WHEN 'json' THEN string_value "
-               "ELSE NULL END AS display_value "
-               "FROM internal_args;",
-               nullptr, nullptr, &error);
-  MaybeRegisterError(error);
-
-  sqlite3_exec(db,
-               "CREATE VIEW ftrace_event AS "
-               "SELECT * FROM raw "
-               "WHERE "
-               "  name NOT LIKE 'chrome_event.%' AND"
-               "  name NOT LIKE 'track_event.%'",
-               nullptr, nullptr, &error);
-  MaybeRegisterError(error);
-}
-
 struct ValueAtMaxTsContext {
   bool initialized;
   int value_type;
@@ -415,7 +238,7 @@
 }
 
 void SetupMetrics(TraceProcessor* tp,
-                  sqlite3* db,
+                  SqliteEngine* engine,
                   std::vector<metrics::SqlMetricFile>* sql_metrics,
                   const std::vector<std::string>& extension_paths) {
   const std::vector<std::string> sanitized_extension_paths =
@@ -445,10 +268,11 @@
     }
   }
 
-  RegisterFunction<metrics::NullIfEmpty>(db, "NULL_IF_EMPTY", 1);
-  RegisterFunction<metrics::UnwrapMetricProto>(db, "UNWRAP_METRIC_PROTO", 2);
+  RegisterFunction<metrics::NullIfEmpty>(engine, "NULL_IF_EMPTY", 1);
+  RegisterFunction<metrics::UnwrapMetricProto>(engine, "UNWRAP_METRIC_PROTO",
+                                               2);
   RegisterFunction<metrics::RunMetric>(
-      db, "RUN_METRIC", -1,
+      engine, "RUN_METRIC", -1,
       std::unique_ptr<metrics::RunMetric::Context>(
           new metrics::RunMetric::Context{tp, sql_metrics}));
 
@@ -456,21 +280,13 @@
   // functions are supported.
   {
     auto ret = sqlite3_create_function_v2(
-        db, "RepeatedField", 1, SQLITE_UTF8, nullptr, nullptr,
+        engine->db(), "RepeatedField", 1, SQLITE_UTF8, nullptr, nullptr,
         metrics::RepeatedFieldStep, metrics::RepeatedFieldFinal, nullptr);
     if (ret)
       PERFETTO_FATAL("Error initializing RepeatedField");
   }
 }
 
-void EnsureSqliteInitialized() {
-  // sqlite3_initialize isn't actually thread-safe despite being documented
-  // as such; we need to make sure multiple TraceProcessorImpl instances don't
-  // call it concurrently and only gets called once per process, instead.
-  static bool init_once = [] { return sqlite3_initialize() == SQLITE_OK; }();
-  PERFETTO_CHECK(init_once);
-}
-
 void InsertIntoTraceMetricsTable(sqlite3* db, const std::string& metric_name) {
   char* insert_sql = sqlite3_mprintf(
       "INSERT INTO trace_metrics(name) VALUES('%q')", metric_name.c_str());
@@ -639,8 +455,8 @@
 }
 
 // Register SQL functions only used in local development instances.
-void RegisterDevFunctions(sqlite3* db) {
-  RegisterFunction<WriteFile>(db, "WRITE_FILE", 2);
+void RegisterDevFunctions(SqliteEngine* engine) {
+  RegisterFunction<WriteFile>(engine, "WRITE_FILE", 2);
 }
 
 sql_modules::NameToModule GetStdlibModules() {
@@ -653,6 +469,17 @@
   return modules;
 }
 
+void InitializePreludeTablesViews(sqlite3* db) {
+  for (const auto& file_to_sql : prelude::tables_views::kFileToSql) {
+    char* errmsg_raw = nullptr;
+    int err = sqlite3_exec(db, file_to_sql.sql, nullptr, nullptr, &errmsg_raw);
+    ScopedSqliteString errmsg(errmsg_raw);
+    if (err != SQLITE_OK) {
+      PERFETTO_FATAL("Failed to initialize prelude %s", errmsg_raw);
+    }
+  }
+}
+
 }  // namespace
 
 template <typename View>
@@ -685,65 +512,78 @@
     context_.content_analyzer.reset(new ProtoContentAnalyzer(&context_));
   }
 
+  sqlite3_str_split_init(engine_.db());
   RegisterAdditionalModules(&context_);
 
-  sqlite3* db = nullptr;
-  EnsureSqliteInitialized();
-  PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
-  InitializeSqlite(db);
-  CreateBuiltinTables(db);
-  CreateBuiltinViews(db);
-  db_.reset(std::move(db));
-
   // New style function registration.
   if (cfg.enable_dev_features) {
-    RegisterDevFunctions(db);
+    RegisterDevFunctions(&engine_);
   }
-  RegisterFunction<Glob>(db, "glob", 2);
-  RegisterFunction<Hash>(db, "HASH", -1);
-  RegisterFunction<Base64Encode>(db, "BASE64_ENCODE", 1);
-  RegisterFunction<Demangle>(db, "DEMANGLE", 1);
-  RegisterFunction<SourceGeq>(db, "SOURCE_GEQ", -1);
-  RegisterFunction<ExportJson>(db, "EXPORT_JSON", 1, context_.storage.get(),
-                               false);
-  RegisterFunction<ExtractArg>(db, "EXTRACT_ARG", 2, context_.storage.get());
-  RegisterFunction<AbsTimeStr>(db, "ABS_TIME_STR", 1,
+  RegisterFunction<Glob>(&engine_, "glob", 2);
+  RegisterFunction<Hash>(&engine_, "HASH", -1);
+  RegisterFunction<Base64Encode>(&engine_, "BASE64_ENCODE", 1);
+  RegisterFunction<Demangle>(&engine_, "DEMANGLE", 1);
+  RegisterFunction<SourceGeq>(&engine_, "SOURCE_GEQ", -1);
+  RegisterFunction<ExportJson>(&engine_, "EXPORT_JSON", 1,
+                               context_.storage.get(), false);
+  RegisterFunction<ExtractArg>(&engine_, "EXTRACT_ARG", 2,
+                               context_.storage.get());
+  RegisterFunction<AbsTimeStr>(&engine_, "ABS_TIME_STR", 1,
                                context_.clock_converter.get());
-  RegisterFunction<ToMonotonic>(db, "TO_MONOTONIC", 1,
+  RegisterFunction<ToMonotonic>(&engine_, "TO_MONOTONIC", 1,
                                 context_.clock_converter.get());
-  RegisterFunction<CreateFunction>(
-      db, "CREATE_FUNCTION", 3,
-      std::unique_ptr<CreateFunction::Context>(
-          new CreateFunction::Context{db_.get(), &create_function_state_}));
+  RegisterFunction<CreateFunction>(&engine_, "CREATE_FUNCTION", 3, &engine_);
   RegisterFunction<CreateViewFunction>(
-      db, "CREATE_VIEW_FUNCTION", 3,
+      &engine_, "CREATE_VIEW_FUNCTION", 3,
       std::unique_ptr<CreateViewFunction::Context>(
-          new CreateViewFunction::Context{db_.get()}));
-  RegisterFunction<Import>(db, "IMPORT", 1,
+          new CreateViewFunction::Context{engine_.db()}));
+  RegisterFunction<Import>(&engine_, "IMPORT", 1,
                            std::unique_ptr<Import::Context>(new Import::Context{
-                               db_.get(), this, &sql_modules_}));
+                               engine_.db(), this, &sql_modules_}));
+  RegisterFunction<ToFtrace>(
+      &engine_, "TO_FTRACE", 1,
+      std::unique_ptr<ToFtrace::Context>(new ToFtrace::Context{
+          context_.storage.get(), SystraceSerializer(&context_)}));
 
   // Old style function registration.
   // TODO(lalitm): migrate this over to using RegisterFunction once aggregate
   // functions are supported.
-  RegisterLastNonNullFunction(db);
-  RegisterValueAtMaxTsFunction(db);
+  RegisterLastNonNullFunction(engine_.db());
+  RegisterValueAtMaxTsFunction(engine_.db());
   {
-    base::Status status = RegisterStackFunctions(db, &context_);
+    base::Status status = RegisterStackFunctions(&engine_, &context_);
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
   {
-    base::Status status = PprofFunctions::Register(db, &context_);
+    base::Status status = PprofFunctions::Register(engine_.db(), &context_);
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
   {
-    base::Status status = LayoutFunctions::Register(db, &context_);
+    base::Status status = LayoutFunctions::Register(engine_.db(), &context_);
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
 
+  const TraceStorage* storage = context_.storage.get();
+
+  // Operator tables.
+  engine_.RegisterVirtualTableModule<SpanJoinOperatorTable>(
+      "span_join", storage, SqliteTable::TableType::kExplicitCreate, false);
+  engine_.RegisterVirtualTableModule<SpanJoinOperatorTable>(
+      "span_left_join", storage, SqliteTable::TableType::kExplicitCreate,
+      false);
+  engine_.RegisterVirtualTableModule<SpanJoinOperatorTable>(
+      "span_outer_join", storage, SqliteTable::TableType::kExplicitCreate,
+      false);
+  engine_.RegisterVirtualTableModule<WindowOperatorTable>(
+      "window", storage, SqliteTable::TableType::kExplicitCreate, true);
+  RegisterCreateViewFunctionModule(&engine_);
+
+  // Initalize the tables and views in the prelude.
+  InitializePreludeTablesViews(engine_.db());
+
   auto stdlib_modules = GetStdlibModules();
   for (auto module_it = stdlib_modules.GetIterator(); module_it; ++module_it) {
     base::Status status =
@@ -752,23 +592,13 @@
       PERFETTO_ELOG("%s", status.c_message());
   }
 
-  SetupMetrics(this, *db_, &sql_metrics_, cfg.skip_builtin_metric_paths);
+  SetupMetrics(this, &engine_, &sql_metrics_, cfg.skip_builtin_metric_paths);
 
-  // Setup the query cache.
-  query_cache_.reset(new QueryCache());
-
-  const TraceStorage* storage = context_.storage.get();
-
-  SqlStatsTable::RegisterTable(*db_, storage);
-  StatsTable::RegisterTable(*db_, storage);
-
-  // Operator tables.
-  SpanJoinOperatorTable::RegisterTable(*db_, storage);
-  WindowOperatorTable::RegisterTable(*db_, storage);
-  CreateViewFunction::RegisterTable(*db_);
-
-  // New style tables but with some custom logic.
-  SqliteRawTable::RegisterTable(*db_, query_cache_.get(), &context_);
+  // Legacy tables.
+  engine_.RegisterVirtualTableModule<SqlStatsTable>(
+      "sqlstats", storage, SqliteTable::TableType::kEponymousOnly, false);
+  engine_.RegisterVirtualTableModule<StatsTable>(
+      "stats", storage, SqliteTable::TableType::kEponymousOnly, false);
 
   // Tables dynamically generated at query time.
   RegisterTableFunction(std::unique_ptr<ExperimentalFlamegraph>(
@@ -810,6 +640,8 @@
   // (O(rows in sched/slice/counter)), then consider calling ShrinkToFit on
   // that table in TraceStorage::ShrinkToFitTables.
   RegisterDbTable(storage->arg_table());
+  RegisterDbTable(storage->raw_table());
+  RegisterDbTable(storage->ftrace_event_table());
   RegisterDbTable(storage->thread_table());
   RegisterDbTable(storage->process_table());
   RegisterDbTable(storage->filedescriptor_table());
@@ -911,7 +743,7 @@
       context_.storage->InternString(TraceTypeToString(context_.trace_type));
   context_.metadata_tracker->SetMetadata(metadata::trace_type,
                                          Variadic::String(trace_type_id));
-  BuildBoundsTable(*db_, context_.storage->GetTraceTimestampBoundsNs());
+  BuildBoundsTable(engine_.db(), context_.storage->GetTraceTimestampBoundsNs());
 }
 
 void TraceProcessorImpl::NotifyEndOfFile() {
@@ -949,7 +781,7 @@
   // TraceProcessorStorageImpl::NotifyEndOfFile, this will be counted in
   // trace bounds: this is important for parsers like ninja which wait until
   // the end to flush all their data.
-  BuildBoundsTable(*db_, context_.storage->GetTraceTimestampBoundsNs());
+  BuildBoundsTable(engine_.db(), context_.storage->GetTraceTimestampBoundsNs());
 
   TraceProcessorStorageImpl::DestroyContext();
 }
@@ -996,19 +828,20 @@
   ScopedStmt stmt;
   IteratorImpl::StmtMetadata metadata;
   base::Status status =
-      PrepareAndStepUntilLastValidStmt(*db_, sql, &stmt, &metadata);
+      PrepareAndStepUntilLastValidStmt(engine_.db(), sql, &stmt, &metadata);
   PERFETTO_DCHECK((status.ok() && stmt) || (!status.ok() && !stmt));
 
-  std::unique_ptr<IteratorImpl> impl(new IteratorImpl(
-      this, *db_, status, std::move(stmt), std::move(metadata), sql_stats_row));
+  std::unique_ptr<IteratorImpl> impl(
+      new IteratorImpl(this, engine_.db(), status, std::move(stmt),
+                       std::move(metadata), sql_stats_row));
   return Iterator(std::move(impl));
 }
 
 void TraceProcessorImpl::InterruptQuery() {
-  if (!db_)
+  if (!engine_.db())
     return;
   query_interrupted_.store(true);
-  sqlite3_interrupt(db_.get());
+  sqlite3_interrupt(engine_.db());
 }
 
 bool TraceProcessorImpl::IsRootMetricField(const std::string& metric_name) {
@@ -1100,7 +933,7 @@
           prev_path.c_str(), path.c_str(), metric.proto_field_name->c_str());
     }
 
-    InsertIntoTraceMetricsTable(*db_, no_ext_name);
+    InsertIntoTraceMetricsTable(engine_.db(), no_ext_name);
   }
 
   sql_metrics_.emplace_back(metric);
@@ -1128,7 +961,7 @@
     auto fn_name = desc.full_name().substr(desc.package_name().size() + 1);
     std::replace(fn_name.begin(), fn_name.end(), '.', '_');
     RegisterFunction<metrics::BuildProto>(
-        db_.get(), fn_name.c_str(), -1,
+        &engine_, fn_name.c_str(), -1,
         std::unique_ptr<metrics::BuildProto::Context>(
             new metrics::BuildProto::Context{this, &pool_, i}));
   }
diff --git a/src/trace_processor/trace_processor_impl.h b/src/trace_processor/trace_processor_impl.h
index 37e7a2c..21cbd92 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -36,6 +36,7 @@
 #include "src/trace_processor/sqlite/db_sqlite_table.h"
 #include "src/trace_processor/sqlite/query_cache.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
+#include "src/trace_processor/sqlite/sqlite_engine.h"
 #include "src/trace_processor/trace_processor_storage_impl.h"
 #include "src/trace_processor/util/sql_modules.h"
 
@@ -107,13 +108,11 @@
 
   template <typename Table>
   void RegisterDbTable(const Table& table) {
-    DbSqliteTable::RegisterTable(*db_, query_cache_.get(), &table,
-                                 Table::Name());
+    engine_.RegisterTable(table, Table::Name());
   }
 
-  void RegisterTableFunction(std::unique_ptr<TableFunction> generator) {
-    DbSqliteTable::RegisterTable(*db_, query_cache_.get(),
-                                 std::move(generator));
+  void RegisterTableFunction(std::unique_ptr<TableFunction> fn) {
+    engine_.RegisterTableFunction(std::move(fn));
   }
 
   template <typename View>
@@ -121,15 +120,7 @@
 
   bool IsRootMetricField(const std::string& metric_name);
 
-  // Keep this first: we need this to be destroyed after we clean up
-  // everything else.
-  ScopedDb db_;
-
-  // State necessary for CREATE_FUNCTION invocations. We store this here as we
-  // need to finalize any prepared statements *before* we destroy the database.
-  CreateFunction::State create_function_state_;
-
-  std::unique_ptr<QueryCache> query_cache_;
+  SqliteEngine engine_;
 
   DescriptorPool pool_;
 
diff --git a/src/trace_processor/types/BUILD.gn b/src/trace_processor/types/BUILD.gn
index ba26e24..0605ea1 100644
--- a/src/trace_processor/types/BUILD.gn
+++ b/src/trace_processor/types/BUILD.gn
@@ -23,7 +23,6 @@
     "task_state.h",
     "tcp_state.h",
     "trace_processor_context.h",
-    "variadic.cc",
     "variadic.h",
     "version_number.h",
   ]
diff --git a/src/trace_processor/util/status_macros.h b/src/trace_processor/util/status_macros.h
index 50e2bb4..2acb799 100644
--- a/src/trace_processor/util/status_macros.h
+++ b/src/trace_processor/util/status_macros.h
@@ -23,7 +23,7 @@
 // error status, returns the status from the current function.
 #define RETURN_IF_ERROR(expr)                           \
   do {                                                  \
-    util::Status status_macro_internal_status = (expr); \
+    base::Status status_macro_internal_status = (expr); \
     if (!status_macro_internal_status.ok())             \
       return status_macro_internal_status;              \
   } while (0)
diff --git a/src/trace_processor/views/BUILD.gn b/src/trace_processor/views/BUILD.gn
index 7cd5408..604a4bb 100644
--- a/src/trace_processor/views/BUILD.gn
+++ b/src/trace_processor/views/BUILD.gn
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import("../../../gn/perfetto_tp_tables.gni")
 import("../../../gn/test.gni")
 
 source_set("views") {
@@ -28,10 +29,15 @@
   ]
 }
 
+perfetto_tp_tables("macros_unittest") {
+  sources = [ "macros_unittest.py" ]
+}
+
 source_set("unittests") {
   testonly = true
   sources = [ "macros_unittest.cc" ]
   deps = [
+    ":macros_unittest",
     ":views",
     "../../../gn:default_deps",
     "../../../gn:gtest_and_gmock",
diff --git a/src/trace_processor/views/macros_internal.h b/src/trace_processor/views/macros_internal.h
index 69f4f90..ca16137 100644
--- a/src/trace_processor/views/macros_internal.h
+++ b/src/trace_processor/views/macros_internal.h
@@ -16,8 +16,6 @@
 
 #include "src/trace_processor/db/view.h"
 
-#include "src/trace_processor/tables/macros_internal.h"
-
 #ifndef SRC_TRACE_PROCESSOR_VIEWS_MACROS_INTERNAL_H_
 #define SRC_TRACE_PROCESSOR_VIEWS_MACROS_INTERNAL_H_
 
@@ -73,6 +71,15 @@
 #pragma GCC system_header
 #endif
 
+// Basic helper macros.
+#define PERFETTO_TP_NOOP(...)
+
+// Invokes FN on each column in the definition of the table. We define a
+// recursive macro as we need to walk up the hierarchy until we hit the root.
+// Currently, we hardcode 5 levels but this can be increased as necessary.
+#define PERFETTO_TP_ALL_COLUMNS(DEF, arg) \
+  DEF(PERFETTO_TP_NOOP, PERFETTO_TP_NOOP, arg)
+
 // Invokes a View column function using data from a table column definition.
 #define PERFETTO_TP_VIEW_INVOKE_VIEW_COL_FN_FROM_TABLE(FN, col_name) \
   FN(col_name, from_table, col_name)
diff --git a/src/trace_processor/views/macros_unittest.cc b/src/trace_processor/views/macros_unittest.cc
index 89c7734..dc4d697 100644
--- a/src/trace_processor/views/macros_unittest.cc
+++ b/src/trace_processor/views/macros_unittest.cc
@@ -16,38 +16,30 @@
 
 #include "src/trace_processor/views/macros.h"
 
+#include "src/trace_processor/views/macros_unittest_py.h"
 #include "test/gtest_and_gmock.h"
 
-#include "src/trace_processor/tables/macros.h"
-
 namespace perfetto {
 namespace trace_processor {
-namespace {
-
-#define PERFETTO_TP_TEST_THREAD_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestThreadTable, "thread")                          \
-  PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)             \
-  C(StringPool::Id, name)                                  \
-  C(int64_t, start_ts, Column::Flag::kSorted)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_THREAD_TABLE_DEF);
+namespace tables {
 
 #define PERFETTO_TP_TEST_EVENT_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestEventTable, "event")                           \
-  PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)            \
+  NAME(MacrosEventTable, "event")                         \
   C(int64_t, ts, Column::Flag::kSorted)                   \
-  C(TestThreadTable::Id, thread_id)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_EVENT_TABLE_DEF);
+  C(MacrosThreadTable::Id, thread_id)
 
-TestEventTable::~TestEventTable() = default;
-TestThreadTable::~TestThreadTable() = default;
+MacrosEventTable::~MacrosEventTable() = default;
+MacrosThreadTable::~MacrosThreadTable() = default;
+
+namespace {
 
 #define PERFETTO_TP_EVENT_VIEW_DEF(NAME, FROM, JOIN, COL, FCOL)             \
   NAME(TestEventView, "event_view")                                         \
   PERFETTO_TP_VIEW_EXPORT_FROM_COLS(PERFETTO_TP_TEST_EVENT_TABLE_DEF, FCOL) \
   COL(thread_name, thread, name)                                            \
   COL(thread_start_ts, thread, start_ts)                                    \
-  FROM(TestEventTable, event)                                               \
-  JOIN(TestThreadTable, thread, id, event, thread_id, View::kIdAlwaysPresent)
+  FROM(MacrosEventTable, event)                                             \
+  JOIN(MacrosThreadTable, thread, id, event, thread_id, View::kIdAlwaysPresent)
 PERFETTO_TP_DECLARE_VIEW(PERFETTO_TP_EVENT_VIEW_DEF);
 PERFETTO_TP_DEFINE_VIEW(TestEventView);
 
@@ -72,8 +64,8 @@
 }
 
 TEST(ViewMacrosUnittest, Schema) {
-  TestThreadTable thread{nullptr, nullptr};
-  TestEventTable event{nullptr, nullptr};
+  MacrosThreadTable thread{nullptr};
+  MacrosEventTable event{nullptr};
 
   TestEventView view{&event, &thread};
   auto schema = view.schema();
@@ -98,5 +90,6 @@
 }
 
 }  // namespace
+}  // namespace tables
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/views/macros_unittest.py b/src/trace_processor/views/macros_unittest.py
new file mode 100644
index 0000000..96606e0
--- /dev/null
+++ b/src/trace_processor/views/macros_unittest.py
@@ -0,0 +1,45 @@
+# 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.
+"""Contains tables for unittesting."""
+
+from python.generators.trace_processor_table.public import Column as C
+from python.generators.trace_processor_table.public import ColumnFlag
+from python.generators.trace_processor_table.public import CppInt64
+from python.generators.trace_processor_table.public import Table
+from python.generators.trace_processor_table.public import CppString
+from python.generators.trace_processor_table.public import CppTableId
+
+MACROS_THREAD_TABLE = Table(
+    python_module=__file__,
+    class_name="MacrosThreadTable",
+    sql_name="thread",
+    columns=[
+        C("name", CppString()),
+        C("start_ts", CppInt64(), flags=ColumnFlag.SORTED),
+    ])
+
+MACROS_EVENT_TABLE = Table(
+    python_module=__file__,
+    class_name="MacrosEventTable",
+    sql_name="event",
+    columns=[
+        C("ts", CppInt64(), flags=ColumnFlag.SORTED),
+        C("thread_id", CppTableId(MACROS_THREAD_TABLE)),
+    ])
+
+# Keep this list sorted.
+ALL_TABLES = [
+    MACROS_THREAD_TABLE,
+    MACROS_EVENT_TABLE,
+]
diff --git a/src/trace_processor/views/slice_views.h b/src/trace_processor/views/slice_views.h
index 287fd02..53dd896 100644
--- a/src/trace_processor/views/slice_views.h
+++ b/src/trace_processor/views/slice_views.h
@@ -19,7 +19,6 @@
 
 #include "src/trace_processor/db/view.h"
 #include "src/trace_processor/tables/metadata_tables_py.h"
-#include "src/trace_processor/tables/slice_tables.h"
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
 #include "src/trace_processor/views/macros.h"
@@ -28,6 +27,23 @@
 namespace trace_processor {
 namespace views {
 
+#define PERFETTO_TP_SLICE_TABLE_DEF(NAME, PARENT, C)  \
+  NAME(SliceTable, "internal_slice")                  \
+  C(int64_t, ts, Column::Flag::kSorted)               \
+  C(int64_t, dur)                                     \
+  C(TrackTable::Id, track_id)                         \
+  C(std::optional<StringPool::Id>, category)          \
+  C(std::optional<StringPool::Id>, name)              \
+  C(uint32_t, depth)                                  \
+  C(int64_t, stack_id)                                \
+  C(int64_t, parent_stack_id)                         \
+  C(std::optional<SliceTable::Id>, parent_id)         \
+  C(uint32_t, arg_set_id)                             \
+  C(std::optional<int64_t>, thread_ts)                \
+  C(std::optional<int64_t>, thread_dur)               \
+  C(std::optional<int64_t>, thread_instruction_count) \
+  C(std::optional<int64_t>, thread_instruction_delta)
+
 // TODO(lalitm): add support in document generator for views.
 #define PERFETTO_TP_THREAD_SLICE_VIEW_DEF(NAME, FROM, JOIN, COL, FCOL)      \
   NAME(ThreadSliceView, "exp_thread_slice")                                 \
diff --git a/src/traceconv/trace_to_systrace.cc b/src/traceconv/trace_to_systrace.cc
index 6b2f380..e3494e8 100644
--- a/src/traceconv/trace_to_systrace.cc
+++ b/src/traceconv/trace_to_systrace.cc
@@ -33,9 +33,6 @@
 #include "perfetto/trace_processor/trace_processor.h"
 #include "src/traceconv/utils.h"
 
-#define FILTER_RAW_EVENTS \
-  " where not (name like \"chrome_event.%\" or name like \"track_event.%\")"
-
 namespace perfetto {
 namespace trace_to_text {
 
@@ -168,8 +165,7 @@
                      Keep truncate_keep) {
   using trace_processor::Iterator;
 
-  static const char kRawEventsCountSql[] =
-      "select count(1) from raw" FILTER_RAW_EVENTS;
+  static const char kRawEventsCountSql[] = "select count(1) from ftrace_event";
   uint32_t raw_events = 0;
   auto e_callback = [&raw_events](Iterator* it, base::StringWriter*) {
     raw_events = static_cast<uint32_t>(it->Get(0).long_value);
@@ -235,7 +231,7 @@
   const uint32_t max_ftrace_events = (140 * 1024 * 1024) / 130;
 
   static const char kRawEventsQuery[] =
-      "select to_ftrace(id) from raw" FILTER_RAW_EVENTS;
+      "select to_ftrace(id) from ftrace_event";
 
   // 1. Write the appropriate header for the file type.
   if (wrapped_in_json) {
diff --git a/src/traced/probes/ftrace/cpu_reader_unittest.cc b/src/traced/probes/ftrace/cpu_reader_unittest.cc
index 459b640..859cc8b 100644
--- a/src/traced/probes/ftrace/cpu_reader_unittest.cc
+++ b/src/traced/probes/ftrace/cpu_reader_unittest.cc
@@ -34,6 +34,7 @@
 #include "test/gtest_and_gmock.h"
 
 #include "protos/perfetto/trace/ftrace/dpu.gen.h"
+#include "protos/perfetto/trace/ftrace/f2fs.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.gen.h"
 #include "protos/perfetto/trace/ftrace/ftrace_event.pbzero.h"
@@ -62,6 +63,7 @@
 using testing::Pair;
 using testing::Property;
 using testing::Return;
+using testing::SizeIs;
 using testing::StartsWith;
 
 namespace perfetto {
@@ -3303,5 +3305,113 @@
   EXPECT_THAT(AllTracePackets(), IsEmpty());
 }
 
+// Kernel code:
+// trace_f2fs_truncate_partial_nodes(... nid = {1,2,3}, depth = 4, err = 0)
+//
+// After kernel commit 0b04d4c0542e("f2fs: Fix
+// f2fs_truncate_partial_nodes ftrace event")
+static ExamplePage g_f2fs_truncate_partial_nodes_new{
+    "b281660544_new",
+    R"(
+00000000: 1555 c3e4 cb07 0000 3c00 0000 0000 0000  .U......<.......
+00000010: 3e33 0b87 2700 0000 0c00 0000 7d02 0000  >3..'.......}...
+00000020: c638 0000 3900 e00f 0000 0000 b165 0000  .8..9........e..
+00000030: 0000 0000 0100 0000 0200 0000 0300 0000  ................
+00000040: 0400 0000 0000 0000 0000 0000 0000 0000  ................
+    )",
+};
+
+TEST_F(CpuReaderParsePagePayloadTest, F2fsTruncatePartialNodesNew) {
+  const ExamplePage* test_case = &g_f2fs_truncate_partial_nodes_new;
+
+  ProtoTranslationTable* table = GetTable(test_case->name);
+  auto page = PageFromXxd(test_case->data);
+
+  FtraceDataSourceConfig ds_config = EmptyConfig();
+  ds_config.event_filter.AddEnabledEvent(table->EventToFtraceId(
+      GroupAndName("f2fs", "f2fs_truncate_partial_nodes")));
+
+  const uint8_t* parse_pos = page.get();
+  std::optional<CpuReader::PageHeader> page_header =
+      CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
+
+  const uint8_t* page_end = page.get() + base::kPageSize;
+  ASSERT_TRUE(page_header.has_value());
+  EXPECT_FALSE(page_header->lost_events);
+  EXPECT_LE(parse_pos + page_header->size, page_end);
+
+  size_t evt_bytes = CpuReader::ParsePagePayload(
+      parse_pos, &page_header.value(), table, &ds_config,
+      CreateBundler(ds_config), &metadata_);
+
+  EXPECT_LT(0u, evt_bytes);
+
+  auto bundle = GetBundle();
+  ASSERT_THAT(bundle.event(), SizeIs(1));
+  auto& event = bundle.event()[0];
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().dev(), 65081u);
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().ino(), 26033u);
+  // This field is disabled in ftrace_proto_gen.cc
+  EXPECT_FALSE(event.f2fs_truncate_partial_nodes().has_nid());
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().depth(), 4);
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().err(), 0);
+}
+
+// Kernel code:
+// trace_f2fs_truncate_partial_nodes(... nid = {1,2,3}, depth = 4, err = 0)
+//
+// Before kernel commit 0b04d4c0542e("f2fs: Fix
+// f2fs_truncate_partial_nodes ftrace event")
+static ExamplePage g_f2fs_truncate_partial_nodes_old{
+    "b281660544_old",
+    R"(
+00000000: 8f90 aa0d 9e00 0000 3c00 0000 0000 0000  ........<.......
+00000010: 3e97 0295 0e01 0000 0c00 0000 7d02 0000  >...........}...
+00000020: 8021 0000 3900 e00f 0000 0000 0d66 0000  .!..9........f..
+00000030: 0000 0000 0100 0000 0200 0000 0300 0000  ................
+00000040: 0400 0000 0000 0000 0000 0000 0000 0000  ................
+    )",
+};
+
+TEST_F(CpuReaderParsePagePayloadTest, F2fsTruncatePartialNodesOld) {
+  const ExamplePage* test_case = &g_f2fs_truncate_partial_nodes_old;
+
+  ProtoTranslationTable* table = GetTable(test_case->name);
+  auto page = PageFromXxd(test_case->data);
+
+  FtraceDataSourceConfig ds_config = EmptyConfig();
+  auto id = table->EventToFtraceId(
+      GroupAndName("f2fs", "f2fs_truncate_partial_nodes"));
+  PERFETTO_LOG("Enabling: %zu", id);
+  ds_config.event_filter.AddEnabledEvent(id);
+
+  const uint8_t* parse_pos = page.get();
+  std::optional<CpuReader::PageHeader> page_header =
+      CpuReader::ParsePageHeader(&parse_pos, table->page_header_size_len());
+
+  const uint8_t* page_end = page.get() + base::kPageSize;
+  ASSERT_TRUE(page_header.has_value());
+  EXPECT_FALSE(page_header->lost_events);
+  EXPECT_LE(parse_pos + page_header->size, page_end);
+
+  size_t evt_bytes = CpuReader::ParsePagePayload(
+      parse_pos, &page_header.value(), table, &ds_config,
+      CreateBundler(ds_config), &metadata_);
+
+  EXPECT_LT(0u, evt_bytes);
+
+  auto bundle = GetBundle();
+  ASSERT_THAT(bundle.event(), SizeIs(1));
+  auto& event = bundle.event()[0];
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().dev(), 65081u);
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().ino(), 26125u);
+  // This field is disabled in ftrace_proto_gen.cc
+  EXPECT_FALSE(event.f2fs_truncate_partial_nodes().has_nid());
+  // Due to a kernel bug, nid[1] is parsed as depth.
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().depth(), 2);
+  // Due to a kernel bug, nid[2] is parsed as err.
+  EXPECT_EQ(event.f2fs_truncate_partial_nodes().err(), 3);
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/traced/probes/ftrace/event_info.cc b/src/traced/probes/ftrace/event_info.cc
index 3551e9f..6a8cca3 100644
--- a/src/traced/probes/ftrace/event_info.cc
+++ b/src/traced/probes/ftrace/event_info.cc
@@ -4266,9 +4266,6 @@
             "ino", 2, ProtoSchemaType::kUint64,
             TranslationStrategy::kInvalidTranslationStrategy},
            {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
-            "nid", 3, ProtoSchemaType::kUint32,
-            TranslationStrategy::kInvalidTranslationStrategy},
-           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
             "depth", 4, ProtoSchemaType::kInt32,
             TranslationStrategy::kInvalidTranslationStrategy},
            {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
@@ -6653,6 +6650,38 @@
        kUnsetFtraceId,
        475,
        kUnsetSize},
+      {"mali_CSF_INTERRUPT_START",
+       "mali",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "kctx_tgid", 1, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "kctx_id", 2, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "info_val", 3, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       482,
+       kUnsetSize},
+      {"mali_CSF_INTERRUPT_END",
+       "mali",
+       {
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "kctx_tgid", 1, ProtoSchemaType::kInt32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "kctx_id", 2, ProtoSchemaType::kUint32,
+            TranslationStrategy::kInvalidTranslationStrategy},
+           {kUnsetOffset, kUnsetSize, FtraceFieldType::kInvalidFtraceFieldType,
+            "info_val", 3, ProtoSchemaType::kUint64,
+            TranslationStrategy::kInvalidTranslationStrategy},
+       },
+       kUnsetFtraceId,
+       483,
+       kUnsetSize},
       {"mdp_cmd_kickoff",
        "mdss",
        {
diff --git a/src/traced/probes/ftrace/test/data/b281660544_new/available_events b/src/traced/probes/ftrace/test/data/b281660544_new/available_events
new file mode 100644
index 0000000..5588a96
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_new/available_events
@@ -0,0 +1 @@
+f2fs:f2fs_truncate_partial_nodes
diff --git a/src/traced/probes/ftrace/test/data/b281660544_new/events/f2fs/f2fs_truncate_partial_nodes/format b/src/traced/probes/ftrace/test/data/b281660544_new/events/f2fs/f2fs_truncate_partial_nodes/format
new file mode 100644
index 0000000..2f5a8ab
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_new/events/f2fs/f2fs_truncate_partial_nodes/format
@@ -0,0 +1,15 @@
+name: f2fs_truncate_partial_nodes
+ID: 637
+format:
+	field:unsigned short common_type;       offset:0;       size:2; signed:0;
+	field:unsigned char common_flags;       offset:2;       size:1; signed:0;
+	field:unsigned char common_preempt_count;       offset:3;       size:1; signed:0;
+	field:int common_pid;   offset:4;       size:4; signed:1;
+
+	field:dev_t dev;        offset:8;       size:4; signed:0;
+	field:ino_t ino;        offset:16;      size:8; signed:0;
+	field:nid_t nid[3];     offset:24;      size:12;        signed:0;
+	field:int depth;        offset:36;      size:4; signed:1;
+	field:int err;  offset:40;      size:4; signed:1;
+
+print fmt: "dev = (%d,%d), ino = %lu, nid[0] = %u, nid[1] = %u, nid[2] = %u, depth = %d, err = %d", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), (unsigned long)REC->ino, (unsigned int)REC->nid[0], (unsigned int)REC->nid[1], (unsigned int)REC->nid[2], REC->depth, REC->err
diff --git a/src/traced/probes/ftrace/test/data/b281660544_new/events/header_page b/src/traced/probes/ftrace/test/data/b281660544_new/events/header_page
new file mode 100644
index 0000000..276dce9
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_new/events/header_page
@@ -0,0 +1,4 @@
+	field: u64 timestamp;   offset:0;       size:8; signed:0;
+	field: local_t commit;  offset:8;       size:8; signed:1;
+	field: int overwrite;   offset:8;       size:1; signed:1;
+	field: char data;       offset:16;      size:4080;      signed:1;
diff --git a/src/traced/probes/ftrace/test/data/b281660544_old/available_events b/src/traced/probes/ftrace/test/data/b281660544_old/available_events
new file mode 100644
index 0000000..5588a96
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_old/available_events
@@ -0,0 +1 @@
+f2fs:f2fs_truncate_partial_nodes
diff --git a/src/traced/probes/ftrace/test/data/b281660544_old/events/f2fs/f2fs_truncate_partial_nodes/format b/src/traced/probes/ftrace/test/data/b281660544_old/events/f2fs/f2fs_truncate_partial_nodes/format
new file mode 100644
index 0000000..15f6a64
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_old/events/f2fs/f2fs_truncate_partial_nodes/format
@@ -0,0 +1,15 @@
+name: f2fs_truncate_partial_nodes
+ID: 637
+format:
+	field:unsigned short common_type;       offset:0;       size:2; signed:0;
+	field:unsigned char common_flags;       offset:2;       size:1; signed:0;
+	field:unsigned char common_preempt_count;       offset:3;       size:1; signed:0;
+	field:int common_pid;   offset:4;       size:4; signed:1;
+
+	field:dev_t dev;        offset:8;       size:4; signed:0;
+	field:ino_t ino;        offset:16;      size:8; signed:0;
+	field:nid_t nid[3];     offset:24;      size:4; signed:0;
+	field:int depth;        offset:28;      size:4; signed:1;
+	field:int err;  offset:32;      size:4; signed:1;
+
+print fmt: "dev = (%d,%d), ino = %lu, nid[0] = %u, nid[1] = %u, nid[2] = %u, depth = %d, err = %d", ((unsigned int) ((REC->dev) >> 20)), ((unsigned int) ((REC->dev) & ((1U << 20) - 1))), (unsigned long)REC->ino, (unsigned int)REC->nid[0], (unsigned int)REC->nid[1], (unsigned int)REC->nid[2], REC->depth, REC->err
diff --git a/src/traced/probes/ftrace/test/data/b281660544_old/events/header_page b/src/traced/probes/ftrace/test/data/b281660544_old/events/header_page
new file mode 100644
index 0000000..276dce9
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/b281660544_old/events/header_page
@@ -0,0 +1,4 @@
+	field: u64 timestamp;   offset:0;       size:8; signed:0;
+	field: local_t commit;  offset:8;       size:8; signed:1;
+	field: int overwrite;   offset:8;       size:1; signed:1;
+	field: char data;       offset:16;      size:4080;      signed:1;
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_CSF_INTERRUPT_END/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_CSF_INTERRUPT_END/format
new file mode 100644
index 0000000..277128b
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_CSF_INTERRUPT_END/format
@@ -0,0 +1,13 @@
+name: mali_CSF_INTERRUPT_END
+ID: 1679
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:pid_t kctx_tgid;	offset:8;	size:4;	signed:1;
+	field:u32 kctx_id;	offset:12;	size:4;	signed:0;
+	field:u64 info_val;	offset:16;	size:8;	signed:0;
+
+print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val
diff --git a/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_CSF_INTERRUPT_START/format b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_CSF_INTERRUPT_START/format
new file mode 100644
index 0000000..0c9e0db
--- /dev/null
+++ b/src/traced/probes/ftrace/test/data/synthetic/events/mali/mali_CSF_INTERRUPT_START/format
@@ -0,0 +1,13 @@
+name: mali_CSF_INTERRUPT_START
+ID: 1678
+format:
+	field:unsigned short common_type;	offset:0;	size:2;	signed:0;
+	field:unsigned char common_flags;	offset:2;	size:1;	signed:0;
+	field:unsigned char common_preempt_count;	offset:3;	size:1;	signed:0;
+	field:int common_pid;	offset:4;	size:4;	signed:1;
+
+	field:pid_t kctx_tgid;	offset:8;	size:4;	signed:1;
+	field:u32 kctx_id;	offset:12;	size:4;	signed:0;
+	field:u64 info_val;	offset:16;	size:8;	signed:0;
+
+print fmt: "kctx=%d_%u info=0x%llx", REC->kctx_tgid, REC->kctx_id, REC->info_val
diff --git a/src/traced/probes/ps/process_stats_data_source.cc b/src/traced/probes/ps/process_stats_data_source.cc
index 8100e42..9ce614c 100644
--- a/src/traced/probes/ps/process_stats_data_source.cc
+++ b/src/traced/probes/ps/process_stats_data_source.cc
@@ -96,6 +96,7 @@
   record_thread_names_ = cfg.record_thread_names();
   dump_all_procs_on_start_ = cfg.scan_all_processes_on_start();
   resolve_process_fds_ = cfg.resolve_process_fds();
+  scan_smaps_rollup_ = cfg.scan_smaps_rollup();
 
   enable_on_demand_dumps_ = true;
   for (auto quirk = cfg.quirks(); quirk; ++quirk) {
@@ -495,6 +496,11 @@
     if (proc_status.empty())
       continue;
 
+    if (scan_smaps_rollup_) {
+      std::string proc_smaps_rollup = ReadProcPidFile(pid, "smaps_rollup");
+      proc_status.append(proc_smaps_rollup);
+    }
+
     if (!WriteMemCounters(pid, proc_status)) {
       // If WriteMemCounters() fails the pid is very likely a kernel thread
       // that has a valid /proc/[pid]/status but no memory values. In this
@@ -606,6 +612,38 @@
           GetOrCreateStatsProcess(pid)->set_vm_swap_kb(counter);
           cached.vm_swap_kb = counter;
         }
+      // The entries below come from smaps_rollup, WriteAllProcessStats merges
+      // everything into the same buffer for convenience.
+      } else if (strcmp(key.data(), "Rss") == 0) {
+         auto counter = ToU32(value.data());
+        if (counter != cached.smr_rss_kb) {
+          GetOrCreateStatsProcess(pid)->set_smr_rss_kb(counter);
+          cached.smr_rss_kb = counter;
+        }
+      } else if (strcmp(key.data(), "Pss") == 0) {
+         auto counter = ToU32(value.data());
+        if (counter != cached.smr_pss_kb) {
+          GetOrCreateStatsProcess(pid)->set_smr_pss_kb(counter);
+          cached.smr_pss_kb = counter;
+        }
+      } else if (strcmp(key.data(), "Pss_Anon") == 0) {
+         auto counter = ToU32(value.data());
+        if (counter != cached.smr_pss_anon_kb) {
+          GetOrCreateStatsProcess(pid)->set_smr_pss_anon_kb(counter);
+          cached.smr_pss_anon_kb = counter;
+        }
+      } else if (strcmp(key.data(), "Pss_File") == 0) {
+         auto counter = ToU32(value.data());
+        if (counter != cached.smr_pss_file_kb) {
+          GetOrCreateStatsProcess(pid)->set_smr_pss_file_kb(counter);
+          cached.smr_pss_file_kb = counter;
+        }
+      } else if (strcmp(key.data(), "Pss_Shmem") == 0) {
+         auto counter = ToU32(value.data());
+        if (counter != cached.smr_pss_shmem_kb) {
+          GetOrCreateStatsProcess(pid)->set_smr_pss_shmem_kb(counter);
+          cached.smr_pss_shmem_kb = counter;
+        }
       }
 
       key.clear();
diff --git a/src/traced/probes/ps/process_stats_data_source.h b/src/traced/probes/ps/process_stats_data_source.h
index 706ee2a..4989b3c 100644
--- a/src/traced/probes/ps/process_stats_data_source.h
+++ b/src/traced/probes/ps/process_stats_data_source.h
@@ -87,6 +87,11 @@
     uint32_t vm_locked_kb = std::numeric_limits<uint32_t>::max();
     uint32_t vm_hvm_kb = std::numeric_limits<uint32_t>::max();
     int oom_score_adj = std::numeric_limits<int>::max();
+    uint32_t smr_rss_kb = std::numeric_limits<uint32_t>::max();
+    uint32_t smr_pss_kb = std::numeric_limits<uint32_t>::max();
+    uint32_t smr_pss_anon_kb = std::numeric_limits<uint32_t>::max();
+    uint32_t smr_pss_file_kb = std::numeric_limits<uint32_t>::max();
+    uint32_t smr_pss_shmem_kb = std::numeric_limits<uint32_t>::max();
 
     // ctime + stime from /proc/pid/stat
     uint64_t cpu_time = std::numeric_limits<uint64_t>::max();
@@ -160,6 +165,7 @@
   bool enable_on_demand_dumps_ = true;
   bool dump_all_procs_on_start_ = false;
   bool resolve_process_fds_ = false;
+  bool scan_smaps_rollup_ = false;
 
   // This set contains PIDs as per the Linux kernel notion of a PID (which is
   // really a TID). In practice this set will contain all TIDs for all processes
diff --git a/src/traced/probes/ps/process_stats_data_source_unittest.cc b/src/traced/probes/ps/process_stats_data_source_unittest.cc
index c87f0bd..5a09ebe 100644
--- a/src/traced/probes/ps/process_stats_data_source_unittest.cc
+++ b/src/traced/probes/ps/process_stats_data_source_unittest.cc
@@ -381,6 +381,10 @@
               return ret.ToStdString();
             }));
 
+    // By default scan_smaps_rollup is off and /proc/<pid>/smaps_rollup
+    // shouldn't be read.
+    EXPECT_CALL(*data_source, ReadProcPidFile(pid, "smaps_rollup")).Times(0);
+
     EXPECT_CALL(*data_source, ReadProcPidFile(pid, "oom_score_adj"))
         .WillRepeatedly(Invoke(
             [checkpoint, kPids, &iter](int32_t inner_pid, const std::string&) {
@@ -535,5 +539,95 @@
   EXPECT_THAT(nstid, ElementsAre(3));
 }
 
+TEST_F(ProcessStatsDataSourceTest, ScanSmapsRollupIsOn) {
+  DataSourceConfig ds_config;
+  ProcessStatsConfig cfg;
+  cfg.set_proc_stats_poll_ms(1);
+  cfg.set_resolve_process_fds(true);
+  cfg.set_scan_smaps_rollup(true);
+  cfg.add_quirks(ProcessStatsConfig::DISABLE_ON_DEMAND);
+  ds_config.set_process_stats_config_raw(cfg.SerializeAsString());
+  auto data_source = GetProcessStatsDataSource(ds_config);
+
+  // Populate a fake /proc/ directory.
+  auto fake_proc = base::TempDir::Create();
+  const int kPids[] = {1, 2};
+  std::vector<std::string> dirs_to_delete;
+  for (int pid : kPids) {
+    base::StackString<256> path("%s/%d", fake_proc.path().c_str(), pid);
+    dirs_to_delete.push_back(path.ToStdString());
+    EXPECT_EQ(mkdir(path.c_str(), 0755), 0)
+        << "mkdir('" << path.c_str() << "') failed";
+  }
+
+  auto checkpoint = task_runner_.CreateCheckpoint("all_done");
+  const auto fake_proc_path = fake_proc.path();
+  EXPECT_CALL(*data_source, OpenProcDir())
+      .WillRepeatedly(Invoke([&fake_proc_path] {
+        return base::ScopedDir(opendir(fake_proc_path.c_str()));
+      }));
+  EXPECT_CALL(*data_source, GetProcMountpoint())
+      .WillRepeatedly(
+          Invoke([&fake_proc_path] { return fake_proc_path.c_str(); }));
+
+  const int kNumIters = 4;
+  int iter = 0;
+  for (int pid : kPids) {
+    EXPECT_CALL(*data_source, ReadProcPidFile(pid, "status"))
+        .WillRepeatedly(
+            Invoke([checkpoint, &iter](int32_t p, const std::string&) {
+              base::StackString<1024> ret(
+                  "Name:	pid_10\nVmSize:	 %d kB\nVmRSS:\t%d  kB\n",
+                  p * 100 + iter * 10 + 1, p * 100 + iter * 10 + 2);
+              return ret.ToStdString();
+            }));
+    EXPECT_CALL(*data_source, ReadProcPidFile(pid, "smaps_rollup"))
+        .WillRepeatedly(
+            Invoke([checkpoint, &iter](int32_t p, const std::string&) {
+              base::StackString<1024> ret(
+                  "Name:	pid_10\nRss:	 %d kB\nPss:\t%d  kB\n",
+                  p * 100 + iter * 10 + 4, p * 100 + iter * 10 + 5);
+              return ret.ToStdString();
+            }));
+
+    EXPECT_CALL(*data_source, ReadProcPidFile(pid, "oom_score_adj"))
+        .WillRepeatedly(Invoke(
+            [checkpoint, kPids, &iter](int32_t inner_pid, const std::string&) {
+              auto oom_score = inner_pid * 100 + iter * 10 + 3;
+              if (inner_pid == kPids[base::ArraySize(kPids) - 1]) {
+                if (++iter == kNumIters)
+                  checkpoint();
+              }
+              return std::to_string(oom_score);
+            }));
+  }
+
+  data_source->Start();
+  task_runner_.RunUntilCheckpoint("all_done");
+  data_source->Flush(1 /* FlushRequestId */, []() {});
+
+  std::vector<protos::gen::ProcessStats::Process> processes;
+  auto trace = writer_raw_->GetAllTracePackets();
+  for (const auto& packet : trace) {
+    for (const auto& process : packet.process_stats().processes()) {
+      processes.push_back(process);
+    }
+  }
+  ASSERT_EQ(processes.size(), kNumIters * base::ArraySize(kPids));
+  iter = 0;
+  for (const auto& proc_counters : processes) {
+    int32_t pid = proc_counters.pid();
+    ASSERT_EQ(static_cast<int>(proc_counters.smr_rss_kb()),
+              pid * 100 + iter * 10 + 4);
+    ASSERT_EQ(static_cast<int>(proc_counters.smr_pss_kb()),
+              pid * 100 + iter * 10 + 5);
+    if (pid == kPids[base::ArraySize(kPids) - 1])
+      iter++;
+  }
+  for (auto path = dirs_to_delete.rbegin(); path != dirs_to_delete.rend();
+       path++)
+    base::Rmdir(*path);
+}
+
 }  // namespace
 }  // namespace perfetto
diff --git a/src/traced/probes/statsd_client/statsd_binder_data_source.cc b/src/traced/probes/statsd_client/statsd_binder_data_source.cc
index 627d4bc..9d3d7f7 100644
--- a/src/traced/probes/statsd_client/statsd_binder_data_source.cc
+++ b/src/traced/probes/statsd_client/statsd_binder_data_source.cc
@@ -242,30 +242,20 @@
                                     const uint8_t* data,
                                     size_t sz) {
   ShellDataDecoder message(data, sz);
+  if (message.has_atom()) {
+    TraceWriter::TracePacketHandle packet = writer_->NewTracePacket();
 
-  bool parse_error = false;
-  auto timestamps_it = message.timestamp_nanos(&parse_error);
-  std::vector<int64_t> timestamps;
-  if (!parse_error) {
-    for (; timestamps_it; ++timestamps_it) {
-      timestamps.push_back(*timestamps_it);
-    }
+    // The root packet gets the timestamp of *now* to aid in
+    // a) Packet sorting in trace_processor
+    // b) So we have some useful record of timestamp in case the statsd
+    //    one gets broken in some exciting way.
+    packet->set_timestamp(static_cast<uint64_t>(base::GetBootTimeNs().count()));
 
-    TraceWriter::TracePacketHandle packet;
-    size_t i = 0;
-    for (auto it = message.atom(); it; ++it) {
-      packet = writer_->NewTracePacket();
-      if (i < timestamps.size()) {
-        packet->set_timestamp(static_cast<uint64_t>(timestamps[i++]));
-      } else {
-        packet->set_timestamp(
-            static_cast<uint64_t>(base::GetBootTimeNs().count()));
-      }
-      auto* statsd_atom = packet->set_statsd_atom();
-      auto* atom = statsd_atom->add_atom();
-      atom->AppendRawProtoBytes(it->data(), it->size());
-      packet->Finalize();
-    }
+    // Now put all the data. We rely on ShellData and StatsdAtom
+    // matching format exactly.
+    packet->AppendBytes(protos::pbzero::TracePacket::kStatsdAtomFieldNumber,
+                        message.begin(),
+                        static_cast<size_t>(message.end() - message.begin()));
   }
 
   // If we have the pending flush in progress resolve that:
diff --git a/src/traced/service/BUILD.gn b/src/traced/service/BUILD.gn
index e0770db..cb142e5 100644
--- a/src/traced/service/BUILD.gn
+++ b/src/traced/service/BUILD.gn
@@ -43,6 +43,10 @@
     "../../tracing/core:service",
     "../../tracing/ipc/service",
   ]
+  if (enable_perfetto_zlib) {
+    deps += [ "../../tracing/core:zlib_compressor" ]
+  }
+
   sources = [
     "builtin_producer.cc",
     "builtin_producer.h",
diff --git a/src/traced/service/service.cc b/src/traced/service/service.cc
index 72f19ac..c9d6335 100644
--- a/src/traced/service/service.cc
+++ b/src/traced/service/service.cc
@@ -43,6 +43,10 @@
 #include <sys/system_properties.h>
 #endif
 
+#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+#include "src/tracing/core/zlib_compressor.h"
+#endif
+
 namespace perfetto {
 namespace {
 #if defined(PERFETTO_SET_SOCKET_PERMISSIONS)
@@ -158,7 +162,11 @@
 
   base::UnixTaskRunner task_runner;
   std::unique_ptr<ServiceIPCHost> svc;
-  svc = ServiceIPCHost::CreateInstance(&task_runner);
+  TracingService::InitOpts init_opts = {};
+#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+  init_opts.compressor_fn = &ZlibCompressFn;
+#endif
+  svc = ServiceIPCHost::CreateInstance(&task_runner, init_opts);
 
   // When built as part of the Android tree, the two socket are created and
   // bound by init and their fd number is passed in two env variables.
diff --git a/src/tracing/core/BUILD.gn b/src/tracing/core/BUILD.gn
index ea1cb90..3fe90ea 100644
--- a/src/tracing/core/BUILD.gn
+++ b/src/tracing/core/BUILD.gn
@@ -83,6 +83,21 @@
   }
 }
 
+if (enable_perfetto_zlib) {
+  source_set("zlib_compressor") {
+    deps = [
+      ":core",
+      "../../../gn:default_deps",
+      "../../../gn:zlib",
+      "../../../include/perfetto/tracing",
+    ]
+    sources = [
+      "zlib_compressor.cc",
+      "zlib_compressor.h",
+    ]
+  }
+}
+
 perfetto_unittest_source_set("unittests") {
   testonly = true
   deps = [
@@ -99,6 +114,14 @@
     "../../base:test_support",
     "../test:test_support",
   ]
+
+  if (enable_perfetto_zlib) {
+    deps += [
+      ":zlib_compressor",
+      "../../../gn:zlib",
+    ]
+  }
+
   sources = [
     "histogram_unittest.cc",
     "id_allocator_unittest.cc",
@@ -110,6 +133,10 @@
     "trace_packet_unittest.cc",
   ]
 
+  if (enable_perfetto_zlib) {
+    sources += [ "zlib_compressor_unittest.cc" ]
+  }
+
   # These tests rely on test_task_runner.h which
   # has no Windows implementation.
   if (!is_win) {
diff --git a/src/tracing/core/metatrace_writer.cc b/src/tracing/core/metatrace_writer.cc
index c270632..8d121a6 100644
--- a/src/tracing/core/metatrace_writer.cc
+++ b/src/tracing/core/metatrace_writer.cc
@@ -26,11 +26,6 @@
 
 namespace perfetto {
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-// static
-constexpr char MetatraceWriter::kDataSourceName[];
-#endif
-
 MetatraceWriter::MetatraceWriter() : weak_ptr_factory_(this) {}
 
 MetatraceWriter::~MetatraceWriter() {
diff --git a/src/tracing/core/shared_memory_abi.cc b/src/tracing/core/shared_memory_abi.cc
index 0c4694b..a9098dc 100644
--- a/src/tracing/core/shared_memory_abi.cc
+++ b/src/tracing/core/shared_memory_abi.cc
@@ -69,16 +69,6 @@
 
 }  // namespace
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-// static
-constexpr uint32_t SharedMemoryABI::kNumChunksForLayout[];
-constexpr const char* SharedMemoryABI::kChunkStateStr[];
-constexpr const size_t SharedMemoryABI::kInvalidPageIdx;
-constexpr const size_t SharedMemoryABI::kMinPageSize;
-constexpr const size_t SharedMemoryABI::kMaxPageSize;
-constexpr const size_t SharedMemoryABI::kPacketSizeDropPacket;
-#endif
-
 SharedMemoryABI::SharedMemoryABI() = default;
 
 SharedMemoryABI::SharedMemoryABI(uint8_t* start,
diff --git a/src/tracing/core/shared_memory_arbiter_impl.cc b/src/tracing/core/shared_memory_arbiter_impl.cc
index f1b830a..61b3974 100644
--- a/src/tracing/core/shared_memory_arbiter_impl.cc
+++ b/src/tracing/core/shared_memory_arbiter_impl.cc
@@ -52,11 +52,6 @@
 SharedMemoryABI::PageLayout SharedMemoryArbiterImpl::default_page_layout =
     SharedMemoryABI::PageLayout::kPageDiv1;
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-// static
-constexpr BufferID SharedMemoryArbiterImpl::kInvalidBufferId;
-#endif
-
 // static
 std::unique_ptr<SharedMemoryArbiter> SharedMemoryArbiter::CreateInstance(
     SharedMemory* shared_memory,
diff --git a/src/tracing/core/trace_buffer.cc b/src/tracing/core/trace_buffer.cc
index 4f0f69f..baa6a34 100644
--- a/src/tracing/core/trace_buffer.cc
+++ b/src/tracing/core/trace_buffer.cc
@@ -42,9 +42,6 @@
     SharedMemoryABI::ChunkHeader::kChunkNeedsPatching;
 }  // namespace.
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-constexpr size_t TraceBuffer::ChunkRecord::kMaxSize;
-#endif
 const size_t TraceBuffer::InlineChunkHeaderSize = sizeof(ChunkRecord);
 
 // static
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index b4ab055..b32a658 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -16,9 +16,6 @@
 
 #include "src/tracing/core/tracing_service_impl.h"
 
-#include "perfetto/base/build_config.h"
-#include "perfetto/tracing/core/forward_decls.h"
-
 #include <errno.h>
 #include <limits.h>
 #include <string.h>
@@ -301,25 +298,21 @@
 
 }  // namespace
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-// These constants instead are defined in the header because are used by tests.
-constexpr size_t TracingServiceImpl::kMaxShmSize;
-constexpr uint32_t TracingServiceImpl::kDataSourceStopTimeoutMs;
-constexpr uint8_t TracingServiceImpl::kSyncMarker[];
-#endif
-
 // static
 std::unique_ptr<TracingService> TracingService::CreateInstance(
     std::unique_ptr<SharedMemory::Factory> shm_factory,
-    base::TaskRunner* task_runner) {
+    base::TaskRunner* task_runner,
+    InitOpts init_opts) {
   return std::unique_ptr<TracingService>(
-      new TracingServiceImpl(std::move(shm_factory), task_runner));
+      new TracingServiceImpl(std::move(shm_factory), task_runner, init_opts));
 }
 
 TracingServiceImpl::TracingServiceImpl(
     std::unique_ptr<SharedMemory::Factory> shm_factory,
-    base::TaskRunner* task_runner)
+    base::TaskRunner* task_runner,
+    InitOpts init_opts)
     : task_runner_(task_runner),
+      init_opts_(init_opts),
       shm_factory_(std::move(shm_factory)),
       uid_(base::GetCurrentUserId()),
       buffer_ids_(kMaxTraceBufferID),
@@ -587,8 +580,8 @@
                             cfg.duration_ms(), max_duration_ms);
   }
 
-  const bool has_trigger_config = cfg.trigger_config().trigger_mode() !=
-                                  TraceConfig::TriggerConfig::UNSPECIFIED;
+  const bool has_trigger_config =
+      GetTriggerMode(cfg) != TraceConfig::TriggerConfig::UNSPECIFIED;
   if (has_trigger_config &&
       (cfg.trigger_config().trigger_timeout_ms() == 0 ||
        cfg.trigger_config().trigger_timeout_ms() > max_duration_ms)) {
@@ -610,6 +603,16 @@
         "The trace config specified an invalid trigger_mode");
   }
 
+  if (cfg.trigger_config().use_clone_snapshot_if_available() &&
+      cfg.trigger_config().trigger_mode() !=
+          TraceConfig::TriggerConfig::STOP_TRACING) {
+    MaybeLogUploadEvent(
+        cfg, uuid, PerfettoStatsdAtom::kTracedEnableTracingInvalidTriggerMode);
+    return PERFETTO_SVC_ERR(
+        "trigger_mode must be STOP_TRACING when "
+        "use_clone_snapshot_if_available=true");
+  }
+
   if (has_trigger_config && cfg.duration_ms() != 0) {
     MaybeLogUploadEvent(
         cfg, uuid, PerfettoStatsdAtom::kTracedEnableTracingDurationWithTrigger);
@@ -617,8 +620,8 @@
         "duration_ms was set, this must not be set for traces with triggers.");
   }
 
-  if (cfg.trigger_config().trigger_mode() ==
-          TraceConfig::TriggerConfig::STOP_TRACING &&
+  if ((GetTriggerMode(cfg) == TraceConfig::TriggerConfig::STOP_TRACING ||
+       GetTriggerMode(cfg) == TraceConfig::TriggerConfig::CLONE_SNAPSHOT) &&
       cfg.write_into_file()) {
     // We don't support this usecase because there are subtle assumptions which
     // break around TracingServiceEvents and windowed sorting (i.e. if we don't
@@ -630,8 +633,8 @@
         cfg, uuid,
         PerfettoStatsdAtom::kTracedEnableTracingStopTracingWriteIntoFile);
     return PERFETTO_SVC_ERR(
-        "Specifying trigger mode STOP_TRACING and write_into_file together is "
-        "unsupported");
+        "Specifying trigger mode STOP_TRACING/CLONE_SNAPSHOT and "
+        "write_into_file together is unsupported");
   }
 
   std::unordered_set<std::string> triggers;
@@ -853,6 +856,17 @@
     tracing_session->bytes_written_into_file = 0;
   }
 
+  if (!cfg.compress_from_cli() &&
+      cfg.compression_type() == TraceConfig::COMPRESSION_TYPE_DEFLATE) {
+    if (init_opts_.compressor_fn) {
+      tracing_session->compress_deflate = true;
+    } else {
+      PERFETTO_LOG(
+          "COMPRESSION_TYPE_DEFLATE is not supported in the current build "
+          "configuration. Skipping compression");
+    }
+  }
+
   // Initialize the log buffers.
   bool did_allocate_all_buffers = true;
   bool invalid_buffer_config = false;
@@ -943,7 +957,7 @@
 
   bool has_start_trigger = false;
   auto weak_this = weak_ptr_factory_.GetWeakPtr();
-  switch (cfg.trigger_config().trigger_mode()) {
+  switch (GetTriggerMode(cfg)) {
     case TraceConfig::TriggerConfig::UNSPECIFIED:
       // no triggers are specified so this isn't a trace that is using triggers.
       PERFETTO_DCHECK(!has_trigger_config);
@@ -960,6 +974,7 @@
           cfg.trigger_config().trigger_timeout_ms());
       break;
     case TraceConfig::TriggerConfig::STOP_TRACING:
+    case TraceConfig::TriggerConfig::CLONE_SNAPSHOT:
       // Update the tracing_session's duration_ms to ensure that if no trigger
       // is received the session will end and be cleaned up equal to the
       // timeout.
@@ -1245,7 +1260,7 @@
   // If this trace was using STOP_TRACING triggers and we've seen
   // one, then the trigger overrides the normal timeout. In this
   // case we just return and let the other task clean up this trace.
-  if (tracing_session_ptr->config.trigger_config().trigger_mode() ==
+  if (GetTriggerMode(tracing_session_ptr->config) ==
           TraceConfig::TriggerConfig::STOP_TRACING &&
       !tracing_session_ptr->received_triggers.empty())
     return;
@@ -1485,7 +1500,7 @@
     std::string triggered_session_name;
     base::Uuid triggered_session_uuid;
     TracingSessionID triggered_session_id = 0;
-    int trigger_mode = 0;
+    auto trigger_mode = TraceConfig::TriggerConfig::UNSPECIFIED;
 
     uint64_t trigger_name_hash = hash.digest();
     size_t count_in_window =
@@ -1544,8 +1559,7 @@
       triggered_session_name = tracing_session.config.unique_session_name();
       triggered_session_uuid.set_lsb_msb(tracing_session.trace_uuid.lsb(),
                                          tracing_session.trace_uuid.msb());
-      trigger_mode = static_cast<int>(
-          tracing_session.config.trigger_config().trigger_mode());
+      trigger_mode = GetTriggerMode(tracing_session.config);
 
       const bool triggers_already_received =
           !tracing_session.received_triggers.empty();
@@ -1553,7 +1567,7 @@
           {static_cast<uint64_t>(now_ns), iter->name(), producer->name_,
            producer->uid_});
       auto weak_this = weak_ptr_factory_.GetWeakPtr();
-      switch (tracing_session.config.trigger_config().trigger_mode()) {
+      switch (trigger_mode) {
         case TraceConfig::TriggerConfig::START_TRACING:
           // If the session has already been triggered and moved past
           // CONFIGURED then we don't need to repeat StartTracing. This would
@@ -1600,6 +1614,24 @@
               // will happen shortly.
               iter->stop_delay_ms());
           break;
+
+        case TraceConfig::TriggerConfig::CLONE_SNAPSHOT:
+          trigger_activated = true;
+          MaybeLogUploadEvent(
+              tracing_session.config, tracing_session.trace_uuid,
+              PerfettoStatsdAtom::kTracedTriggerCloneSnapshot, iter->name());
+          task_runner_->PostDelayedTask(
+              [weak_this, tsid] {
+                if (!weak_this)
+                  return;
+                auto* tsess = weak_this->GetTracingSession(tsid);
+                if (!tsess || !tsess->consumer_maybe_null)
+                  return;
+                tsess->consumer_maybe_null->NotifyCloneSnapshotTrigger();
+              },
+              iter->stop_delay_ms());
+          break;
+
         case TraceConfig::TriggerConfig::UNSPECIFIED:
           PERFETTO_ELOG("Trigger activated but trigger mode unspecified.");
           break;
@@ -2306,6 +2338,8 @@
 
   MaybeFilterPackets(tracing_session, &packets);
 
+  MaybeCompressPackets(tracing_session, &packets);
+
   if (!*has_more) {
     // We've observed some extremely high memory usage by scudo after
     // MaybeFilterPackets in the past. The original bug (b/195145848) is fixed
@@ -2358,6 +2392,16 @@
   }
 }
 
+void TracingServiceImpl::MaybeCompressPackets(
+    TracingSession* tracing_session,
+    std::vector<TracePacket>* packets) {
+  if (!tracing_session->compress_deflate) {
+    return;
+  }
+
+  init_opts_.compressor_fn(packets);
+}
+
 bool TracingServiceImpl::WriteIntoFile(TracingSession* tracing_session,
                                        std::vector<TracePacket> packets) {
   if (!tracing_session->write_into_file) {
@@ -3497,7 +3541,7 @@
     TracingSession* session = FindTracingSessionWithMaxBugreportScore();
     if (!session) {
       consumer->consumer_->OnSessionCloned(
-          false, "No tracing sessions eligible for bugreport found");
+          {false, "No tracing sessions eligible for bugreport found", {}});
       return;
     }
     tsid = session->id;
@@ -3510,15 +3554,18 @@
                  final_flush_outcome);
     if (!weak_this || !weak_consumer)
       return;
-    base::Status result =
-        weak_this->DoCloneSession(&*weak_consumer, tsid, final_flush_outcome);
-    weak_consumer->consumer_->OnSessionCloned(result.ok(), result.message());
+    base::Uuid uuid;
+    base::Status result = weak_this->DoCloneSession(&*weak_consumer, tsid,
+                                                    final_flush_outcome, &uuid);
+    weak_consumer->consumer_->OnSessionCloned(
+        {result.ok(), result.message(), uuid});
   });
 }
 
 base::Status TracingServiceImpl::DoCloneSession(ConsumerEndpointImpl* consumer,
                                                 TracingSessionID src_tsid,
-                                                bool final_flush_outcome) {
+                                                bool final_flush_outcome,
+                                                base::Uuid* new_uuid) {
   PERFETTO_DLOG("CloneSession(%" PRIu64 ") started, consumer uid: %d", src_tsid,
                 static_cast<int>(consumer->uid_));
 
@@ -3569,6 +3616,7 @@
 
   cloned_session->state = TracingSession::CLONED_READ_ONLY;
   cloned_session->trace_uuid = base::Uuidv4();  // Generate a new UUID.
+  *new_uuid = cloned_session->trace_uuid;
 
   for (auto& kv : buf_snaps) {
     BufferID buf_global_id = kv.first;
@@ -3589,6 +3637,7 @@
   cloned_session->flushes_requested = src->flushes_requested;
   cloned_session->flushes_succeeded = src->flushes_succeeded;
   cloned_session->flushes_failed = src->flushes_failed;
+  cloned_session->compress_deflate = src->compress_deflate;
   if (src->trace_filter) {
     // Copy the trace filter.
     cloned_session->trace_filter.reset(
@@ -3814,6 +3863,15 @@
   observable_events->set_all_data_sources_started(true);
 }
 
+void TracingServiceImpl::ConsumerEndpointImpl::NotifyCloneSnapshotTrigger() {
+  if (!(observable_events_mask_ & ObservableEvents::TYPE_CLONE_TRIGGER_HIT)) {
+    return;
+  }
+  auto* observable_events = AddObservableEvents();
+  auto* clone_trig = observable_events->mutable_clone_trigger_hit();
+  clone_trig->set_tracing_session_id(static_cast<int64_t>(tracing_session_id_));
+}
+
 ObservableEvents*
 TracingServiceImpl::ConsumerEndpointImpl::AddObservableEvents() {
   PERFETTO_DCHECK_THREAD(thread_checker_);
@@ -3910,11 +3968,13 @@
   TracingServiceCapabilities caps;
   caps.set_has_query_capabilities(true);
   caps.set_has_trace_config_output_path(true);
+  caps.set_has_clone_session(true);
   caps.add_observable_events(ObservableEvents::TYPE_DATA_SOURCES_INSTANCES);
   caps.add_observable_events(ObservableEvents::TYPE_ALL_DATA_SOURCES_STARTED);
-  static_assert(ObservableEvents::Type_MAX ==
-                    ObservableEvents::TYPE_ALL_DATA_SOURCES_STARTED,
-                "");
+  caps.add_observable_events(ObservableEvents::TYPE_CLONE_TRIGGER_HIT);
+  static_assert(
+      ObservableEvents::Type_MAX == ObservableEvents::TYPE_CLONE_TRIGGER_HIT,
+      "");
   callback(caps);
 }
 
diff --git a/src/tracing/core/tracing_service_impl.h b/src/tracing/core/tracing_service_impl.h
index 7cefb9b..8d067fd 100644
--- a/src/tracing/core/tracing_service_impl.h
+++ b/src/tracing/core/tracing_service_impl.h
@@ -87,8 +87,9 @@
                          // tracing_integration_test.cc and b/195065199
 
   // This is a rough threshold to determine how many bytes to read from the
-  // buffers on each iteration when writing into a file. Since filtering
-  // allocates memory, this limits the amount of memory allocated.
+  // buffers on each iteration when writing into a file. Since filtering and
+  // compression allocate memory, this effectively limits the amount of memory
+  // allocated.
   static constexpr size_t kWriteIntoFileChunkSize = 1024 * 1024ul;
 
   // The implementation behind the service endpoint exposed to each producer.
@@ -209,6 +210,7 @@
     ~ConsumerEndpointImpl() override;
 
     void NotifyOnTracingDisabled(const std::string& error);
+    void NotifyCloneSnapshotTrigger();
 
     // TracingService::ConsumerEndpoint implementation.
     void EnableTracing(const TraceConfig&, base::ScopedFile) override;
@@ -264,7 +266,8 @@
   };
 
   explicit TracingServiceImpl(std::unique_ptr<SharedMemory::Factory>,
-                              base::TaskRunner*);
+                              base::TaskRunner*,
+                              InitOpts = {});
   ~TracingServiceImpl() override;
 
   // Called by ProducerEndpointImpl.
@@ -565,6 +568,9 @@
     // Whether we put the system info into the trace output yet.
     bool did_emit_system_info = false;
 
+    // Whether we should compress TracePackets after reading them.
+    bool compress_deflate = false;
+
     // The number of received triggers we've emitted into the trace output.
     size_t num_triggers_emitted_into_trace = 0;
 
@@ -723,7 +729,8 @@
   TraceBuffer* GetBufferByID(BufferID);
   base::Status DoCloneSession(ConsumerEndpointImpl*,
                               TracingSessionID,
-                              bool final_flush_outcome);
+                              bool final_flush_outcome,
+                              base::Uuid*);
 
   // Returns true if `*tracing_session` is waiting for a trigger that hasn't
   // happened.
@@ -744,6 +751,10 @@
   void MaybeFilterPackets(TracingSession* tracing_session,
                           std::vector<TracePacket>* packets);
 
+  // If `*tracing_session` has compression enabled, compress `*packets`.
+  void MaybeCompressPackets(TracingSession* tracing_session,
+                            std::vector<TracePacket>* packets);
+
   // If `*tracing_session` is configured to write into a file, writes `packets`
   // into the file.
   //
@@ -765,6 +776,7 @@
                                      TracingSessionID);
 
   base::TaskRunner* const task_runner_;
+  const InitOpts init_opts_;
   std::unique_ptr<SharedMemory::Factory> shm_factory_;
   ProducerID last_producer_id_ = 0;
   DataSourceInstanceID last_data_source_instance_id_ = 0;
diff --git a/src/tracing/core/tracing_service_impl_unittest.cc b/src/tracing/core/tracing_service_impl_unittest.cc
index 6542723..fb175e9 100644
--- a/src/tracing/core/tracing_service_impl_unittest.cc
+++ b/src/tracing/core/tracing_service_impl_unittest.cc
@@ -49,6 +49,11 @@
 #include "protos/perfetto/trace/trace_uuid.gen.h"
 #include "protos/perfetto/trace/trigger.gen.h"
 
+#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+#include <zlib.h>
+#include "src/tracing/core/zlib_compressor.h"
+#endif
+
 using ::testing::_;
 using ::testing::AssertionFailure;
 using ::testing::AssertionResult;
@@ -103,6 +108,53 @@
   return HasTriggerModeInternal(arg, mode);
 }
 
+#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+std::string Decompress(const std::string& data) {
+  uint8_t out[1024];
+
+  z_stream stream{};
+  stream.next_in = reinterpret_cast<uint8_t*>(const_cast<char*>(data.data()));
+  stream.avail_in = static_cast<unsigned int>(data.size());
+
+  EXPECT_EQ(inflateInit(&stream), Z_OK);
+  std::string s;
+
+  int ret;
+  do {
+    stream.next_out = out;
+    stream.avail_out = sizeof(out);
+    ret = inflate(&stream, Z_NO_FLUSH);
+    EXPECT_NE(ret, Z_STREAM_ERROR);
+    EXPECT_NE(ret, Z_NEED_DICT);
+    EXPECT_NE(ret, Z_DATA_ERROR);
+    EXPECT_NE(ret, Z_MEM_ERROR);
+    s.append(reinterpret_cast<char*>(out), sizeof(out) - stream.avail_out);
+  } while (ret != Z_STREAM_END);
+
+  inflateEnd(&stream);
+  return s;
+}
+
+std::vector<protos::gen::TracePacket> DecompressTrace(
+    const std::vector<protos::gen::TracePacket> compressed) {
+  std::vector<protos::gen::TracePacket> decompressed;
+
+  for (const protos::gen::TracePacket& c : compressed) {
+    if (c.compressed_packets().empty()) {
+      decompressed.push_back(c);
+      continue;
+    }
+
+    std::string s = Decompress(c.compressed_packets());
+    protos::gen::Trace t;
+    EXPECT_TRUE(t.ParseFromString(s));
+    decompressed.insert(decompressed.end(), t.packet().begin(),
+                        t.packet().end());
+  }
+  return decompressed;
+}
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+
 }  // namespace
 
 class TracingServiceImplTest : public testing::Test {
@@ -110,11 +162,14 @@
   using DataSourceInstanceState =
       TracingServiceImpl::DataSourceInstance::DataSourceInstanceState;
 
-  TracingServiceImplTest() {
+  TracingServiceImplTest() { InitializeSvcWithOpts({}); }
+
+  void InitializeSvcWithOpts(TracingService::InitOpts init_opts) {
     auto shm_factory =
         std::unique_ptr<SharedMemory::Factory>(new TestSharedMemory::Factory());
     svc.reset(static_cast<TracingServiceImpl*>(
-        TracingService::CreateInstance(std::move(shm_factory), &task_runner)
+        TracingService::CreateInstance(std::move(shm_factory), &task_runner,
+                                       init_opts)
             .release()));
     svc->min_write_period_ms_ = 1;
   }
@@ -1631,6 +1686,312 @@
   ASSERT_EQ(6u, connect_producer_and_get_id("6"));
 }
 
+TEST_F(TracingServiceImplTest, CompressionConfiguredButUnsupported) {
+  // Initialize the service without support for compression.
+  TracingService::InitOpts init_opts;
+  init_opts.compressor_fn = nullptr;
+  InitializeSvcWithOpts(init_opts);
+
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+  producer->RegisterDataSource("data_source");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(4096);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("data_source");
+  ds_config->set_target_buffer(0);
+  // Ask for compression in the config.
+  trace_config.set_compression_type(TraceConfig::COMPRESSION_TYPE_DEFLATE);
+  consumer->EnableTracing(trace_config);
+
+  producer->WaitForTracingSetup();
+  producer->WaitForDataSourceSetup("data_source");
+  producer->WaitForDataSourceStart("data_source");
+
+  std::unique_ptr<TraceWriter> writer =
+      producer->CreateTraceWriter("data_source");
+  {
+    auto tp = writer->NewTracePacket();
+    tp->set_for_testing()->set_str("payload-1");
+  }
+  {
+    auto tp = writer->NewTracePacket();
+    tp->set_for_testing()->set_str("payload-2");
+  }
+
+  writer->Flush();
+  writer.reset();
+
+  consumer->DisableTracing();
+  producer->WaitForDataSourceStop("data_source");
+  consumer->WaitForTracingDisabled();
+
+  // The packets should NOT be compressed.
+  std::vector<protos::gen::TracePacket> packets = consumer->ReadBuffers();
+  EXPECT_THAT(packets, Not(IsEmpty()));
+  EXPECT_THAT(
+      packets,
+      Each(Property(&protos::gen::TracePacket::has_compressed_packets, false)));
+  EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing,
+                                         Property(&protos::gen::TestEvent::str,
+                                                  Eq("payload-1")))));
+  EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing,
+                                         Property(&protos::gen::TestEvent::str,
+                                                  Eq("payload-2")))));
+}
+
+#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+TEST_F(TracingServiceImplTest, CompressionFromCli) {
+  TracingService::InitOpts init_opts;
+  init_opts.compressor_fn = ZlibCompressFn;
+  InitializeSvcWithOpts(init_opts);
+
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+  producer->RegisterDataSource("data_source");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(4096);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("data_source");
+  ds_config->set_target_buffer(0);
+  trace_config.set_compression_type(TraceConfig::COMPRESSION_TYPE_DEFLATE);
+  // When compress_from_cli is enabled, the service shouldn't do compression
+  trace_config.set_compress_from_cli(true);
+  consumer->EnableTracing(trace_config);
+
+  producer->WaitForTracingSetup();
+  producer->WaitForDataSourceSetup("data_source");
+  producer->WaitForDataSourceStart("data_source");
+
+  std::unique_ptr<TraceWriter> writer =
+      producer->CreateTraceWriter("data_source");
+  {
+    auto tp = writer->NewTracePacket();
+    tp->set_for_testing()->set_str("payload-1");
+  }
+  {
+    auto tp = writer->NewTracePacket();
+    tp->set_for_testing()->set_str("payload-2");
+  }
+
+  writer->Flush();
+  writer.reset();
+
+  consumer->DisableTracing();
+  producer->WaitForDataSourceStop("data_source");
+  consumer->WaitForTracingDisabled();
+
+  std::vector<protos::gen::TracePacket> packets = consumer->ReadBuffers();
+  EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing,
+                                         Property(&protos::gen::TestEvent::str,
+                                                  Eq("payload-1")))));
+  EXPECT_THAT(packets, Contains(Property(&protos::gen::TracePacket::for_testing,
+                                         Property(&protos::gen::TestEvent::str,
+                                                  Eq("payload-2")))));
+}
+
+TEST_F(TracingServiceImplTest, CompressionReadIpc) {
+  TracingService::InitOpts init_opts;
+  init_opts.compressor_fn = ZlibCompressFn;
+  InitializeSvcWithOpts(init_opts);
+
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+  producer->RegisterDataSource("data_source");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(4096);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("data_source");
+  ds_config->set_target_buffer(0);
+  trace_config.set_compression_type(TraceConfig::COMPRESSION_TYPE_DEFLATE);
+  consumer->EnableTracing(trace_config);
+
+  producer->WaitForTracingSetup();
+  producer->WaitForDataSourceSetup("data_source");
+  producer->WaitForDataSourceStart("data_source");
+
+  std::unique_ptr<TraceWriter> writer =
+      producer->CreateTraceWriter("data_source");
+  {
+    auto tp = writer->NewTracePacket();
+    tp->set_for_testing()->set_str("payload-1");
+  }
+  {
+    auto tp = writer->NewTracePacket();
+    tp->set_for_testing()->set_str("payload-2");
+  }
+
+  writer->Flush();
+  writer.reset();
+
+  consumer->DisableTracing();
+  producer->WaitForDataSourceStop("data_source");
+  consumer->WaitForTracingDisabled();
+
+  std::vector<protos::gen::TracePacket> compressed_packets =
+      consumer->ReadBuffers();
+  EXPECT_THAT(compressed_packets, Not(IsEmpty()));
+  EXPECT_THAT(compressed_packets,
+              Each(Property(&protos::gen::TracePacket::compressed_packets,
+                            Not(IsEmpty()))));
+  std::vector<protos::gen::TracePacket> decompressed_packets =
+      DecompressTrace(compressed_packets);
+  EXPECT_THAT(decompressed_packets,
+              Contains(Property(
+                  &protos::gen::TracePacket::for_testing,
+                  Property(&protos::gen::TestEvent::str, Eq("payload-1")))));
+  EXPECT_THAT(decompressed_packets,
+              Contains(Property(
+                  &protos::gen::TracePacket::for_testing,
+                  Property(&protos::gen::TestEvent::str, Eq("payload-2")))));
+}
+
+TEST_F(TracingServiceImplTest, CompressionWriteIntoFile) {
+  TracingService::InitOpts init_opts;
+  init_opts.compressor_fn = ZlibCompressFn;
+  InitializeSvcWithOpts(init_opts);
+
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+  producer->RegisterDataSource("data_source");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(4096);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("data_source");
+  ds_config->set_target_buffer(0);
+  trace_config.set_write_into_file(true);
+  trace_config.set_compression_type(TraceConfig::COMPRESSION_TYPE_DEFLATE);
+  base::TempFile tmp_file = base::TempFile::Create();
+  consumer->EnableTracing(trace_config, base::ScopedFile(dup(tmp_file.fd())));
+
+  producer->WaitForTracingSetup();
+  producer->WaitForDataSourceSetup("data_source");
+  producer->WaitForDataSourceStart("data_source");
+
+  std::unique_ptr<TraceWriter> writer =
+      producer->CreateTraceWriter("data_source");
+  {
+    auto tp = writer->NewTracePacket();
+    tp->set_for_testing()->set_str("payload-1");
+  }
+  {
+    auto tp = writer->NewTracePacket();
+    tp->set_for_testing()->set_str("payload-2");
+  }
+
+  writer->Flush();
+  writer.reset();
+
+  consumer->DisableTracing();
+  producer->WaitForDataSourceStop("data_source");
+  consumer->WaitForTracingDisabled();
+
+  // Verify the contents of the file.
+  std::string trace_raw;
+  ASSERT_TRUE(base::ReadFile(tmp_file.path().c_str(), &trace_raw));
+  protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromString(trace_raw));
+  EXPECT_THAT(trace.packet(), Not(IsEmpty()));
+  EXPECT_THAT(trace.packet(),
+              Each(Property(&protos::gen::TracePacket::compressed_packets,
+                            Not(IsEmpty()))));
+  std::vector<protos::gen::TracePacket> decompressed_packets =
+      DecompressTrace(trace.packet());
+  EXPECT_THAT(decompressed_packets,
+              Contains(Property(
+                  &protos::gen::TracePacket::for_testing,
+                  Property(&protos::gen::TestEvent::str, Eq("payload-1")))));
+  EXPECT_THAT(decompressed_packets,
+              Contains(Property(
+                  &protos::gen::TracePacket::for_testing,
+                  Property(&protos::gen::TestEvent::str, Eq("payload-2")))));
+}
+
+TEST_F(TracingServiceImplTest, CloneSessionWithCompression) {
+  TracingService::InitOpts init_opts;
+  init_opts.compressor_fn = ZlibCompressFn;
+  InitializeSvcWithOpts(init_opts);
+
+  // The consumer the creates the initial tracing session.
+  std::unique_ptr<MockConsumer> consumer = CreateMockConsumer();
+  consumer->Connect(svc.get());
+
+  // The consumer that clones it and reads back the data.
+  std::unique_ptr<MockConsumer> consumer2 = CreateMockConsumer();
+  consumer2->Connect(svc.get());
+
+  std::unique_ptr<MockProducer> producer = CreateMockProducer();
+  producer->Connect(svc.get(), "mock_producer");
+
+  producer->RegisterDataSource("ds_1");
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(32);
+  auto* ds_cfg = trace_config.add_data_sources()->mutable_config();
+  ds_cfg->set_name("ds_1");
+  trace_config.set_compression_type(TraceConfig::COMPRESSION_TYPE_DEFLATE);
+
+  consumer->EnableTracing(trace_config);
+  producer->WaitForTracingSetup();
+
+  producer->WaitForDataSourceSetup("ds_1");
+
+  producer->WaitForDataSourceStart("ds_1");
+
+  std::unique_ptr<TraceWriter> writer = producer->CreateTraceWriter("ds_1");
+
+  // Add some data.
+  static constexpr size_t kNumTestPackets = 20;
+  for (size_t i = 0; i < kNumTestPackets; i++) {
+    auto tp = writer->NewTracePacket();
+    std::string payload("payload" + std::to_string(i));
+    tp->set_for_testing()->set_str(payload.c_str(), payload.size());
+    tp->set_timestamp(static_cast<uint64_t>(i));
+  }
+
+  auto clone_done = task_runner.CreateCheckpoint("clone_done");
+  EXPECT_CALL(*consumer2, OnSessionCloned(_))
+      .WillOnce(Invoke([clone_done](const Consumer::OnSessionClonedArgs&) {
+        clone_done();
+      }));
+  consumer2->CloneSession(1);
+  // CloneSession() will implicitly issue a flush. Linearize with that.
+  producer->WaitForFlush(std::vector<TraceWriter*>{writer.get()});
+  task_runner.RunUntilCheckpoint("clone_done");
+
+  // Delete the initial tracing session.
+  consumer->DisableTracing();
+  consumer->FreeBuffers();
+  producer->WaitForDataSourceStop("ds_1");
+  consumer->WaitForTracingDisabled();
+
+  // Read back the cloned trace and check that it's compressed
+  std::vector<protos::gen::TracePacket> compressed_packets =
+      consumer2->ReadBuffers();
+  EXPECT_THAT(compressed_packets, Not(IsEmpty()));
+  EXPECT_THAT(compressed_packets,
+              Each(Property(&protos::gen::TracePacket::compressed_packets,
+                            Not(IsEmpty()))));
+}
+
+#endif  // PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+
 // Note: file_write_period_ms is set to a large enough to have exactly one flush
 // of the tracing buffers (and therefore at most one synchronization section),
 // unless the test runs unrealistically slowly, or the implementation of the
@@ -4071,7 +4432,9 @@
   // Message 0: root Trace proto.
   filt.AddNestedField(1 /* root trace.packet*/, 1);
   filt.EndMessage();
-  // Message 1: TracePacket proto. Allow only the `for_testing` sub-field.
+  // Message 1: TracePacket proto. Allow only the `for_testing` and `trace_uuid`
+  // sub-fields.
+  filt.AddSimpleField(protos::pbzero::TracePacket::kTraceUuidFieldNumber);
   filt.AddSimpleField(protos::pbzero::TracePacket::kForTestingFieldNumber);
   filt.EndMessage();
   trace_config.mutable_trace_filter()->set_bytecode(filt.Serialize());
@@ -4100,8 +4463,17 @@
   }
 
   auto clone_done = task_runner.CreateCheckpoint("clone_done");
-  EXPECT_CALL(*consumer2, OnSessionCloned(true, ""))
-      .WillOnce(InvokeWithoutArgs(clone_done));
+  base::Uuid clone_uuid;
+  EXPECT_CALL(*consumer2, OnSessionCloned(_))
+      .WillOnce(Invoke(
+          [clone_done, &clone_uuid](const Consumer::OnSessionClonedArgs& args) {
+            ASSERT_TRUE(args.success);
+            ASSERT_TRUE(args.error.empty());
+            ASSERT_NE(args.uuid.msb(), 0);
+            ASSERT_NE(args.uuid.lsb(), 0);
+            clone_uuid = args.uuid;
+            clone_done();
+          }));
   consumer2->CloneSession(1);
   // CloneSession() will implicitly issue a flush. Linearize with that.
   producer->WaitForFlush({writers[0].get(), writers[1].get()});
@@ -4145,6 +4517,16 @@
   // Check that the `timestamp` field is filtered out.
   EXPECT_THAT(packets,
               Each(Property(&protos::gen::TracePacket::has_timestamp, false)));
+
+  // Check that the UUID in the trace matches the UUID passed to to the
+  // OnCloneSession consumer API.
+  EXPECT_THAT(
+      packets,
+      Contains(Property(
+          &protos::gen::TracePacket::trace_uuid,
+          AllOf(
+              Property(&protos::gen::TraceUuid::msb, Eq(clone_uuid.msb())),
+              Property(&protos::gen::TraceUuid::lsb, Eq(clone_uuid.lsb()))))));
 }
 
 TEST_F(TracingServiceImplTest, InvalidBufferSizes) {
diff --git a/src/tracing/core/virtual_destructors.cc b/src/tracing/core/virtual_destructors.cc
index 5b7534f..d38a776 100644
--- a/src/tracing/core/virtual_destructors.cc
+++ b/src/tracing/core/virtual_destructors.cc
@@ -38,11 +38,6 @@
 
 // TODO(primiano): make pure virtual after various 3way patches.
 void ConsumerEndpoint::CloneSession(TracingSessionID) {}
-void Consumer::OnSessionCloned(bool, const std::string&) {}
-
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-constexpr size_t TracingService::kDefaultShmSize;
-constexpr size_t TracingService::kDefaultShmPageSize;
-#endif
+void Consumer::OnSessionCloned(const OnSessionClonedArgs&) {}
 
 }  // namespace perfetto
diff --git a/src/tracing/core/zlib_compressor.cc b/src/tracing/core/zlib_compressor.cc
new file mode 100644
index 0000000..1a9689e
--- /dev/null
+++ b/src/tracing/core/zlib_compressor.cc
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#include "src/tracing/core/zlib_compressor.h"
+
+#if !PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
+#error "Zlib must be enabled to compile this file."
+#endif
+
+#include <zlib.h>
+
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "protos/perfetto/trace/trace_packet.pbzero.h"
+
+namespace perfetto {
+
+namespace {
+
+struct Preamble {
+  uint32_t size;
+  std::array<uint8_t, 16> buf;
+};
+
+template <uint32_t id>
+Preamble GetPreamble(size_t sz) {
+  Preamble preamble;
+  uint8_t* ptr = preamble.buf.data();
+  constexpr uint32_t tag = protozero::proto_utils::MakeTagLengthDelimited(id);
+  ptr = protozero::proto_utils::WriteVarInt(tag, ptr);
+  ptr = protozero::proto_utils::WriteVarInt(sz, ptr);
+  preamble.size =
+      static_cast<uint32_t>(reinterpret_cast<uintptr_t>(ptr) -
+                            reinterpret_cast<uintptr_t>(preamble.buf.data()));
+  PERFETTO_DCHECK(preamble.size < preamble.buf.size());
+  return preamble;
+}
+
+Slice PreambleToSlice(const Preamble& preamble) {
+  Slice slice = Slice::Allocate(preamble.size);
+  memcpy(slice.own_data(), preamble.buf.data(), preamble.size);
+  return slice;
+}
+
+// A compressor for `TracePacket`s that uses zlib. The class is exposed for
+// testing.
+class ZlibPacketCompressor {
+ public:
+  ZlibPacketCompressor();
+  ~ZlibPacketCompressor();
+
+  // Can be called multiple times, before Finish() is called.
+  void PushPacket(const TracePacket& packet);
+
+  // Returned the compressed data. Can be called at most once. After this call,
+  // the object is unusable (PushPacket should not be called) and must be
+  // destroyed.
+  TracePacket Finish();
+
+ private:
+  void PushData(const void* data, uint32_t size);
+  void NewOutputSlice();
+  void PushCurSlice();
+
+  z_stream stream_;
+  size_t total_new_slices_size_ = 0;
+  std::vector<Slice> new_slices_;
+  std::unique_ptr<uint8_t[]> cur_slice_;
+};
+
+ZlibPacketCompressor::ZlibPacketCompressor() {
+  memset(&stream_, 0, sizeof(stream_));
+  int status = deflateInit(&stream_, 6);
+  PERFETTO_CHECK(status == Z_OK);
+}
+
+ZlibPacketCompressor::~ZlibPacketCompressor() {
+  int status = deflateEnd(&stream_);
+  PERFETTO_CHECK(status == Z_OK);
+}
+
+void ZlibPacketCompressor::PushPacket(const TracePacket& packet) {
+  // We need to be able to tokenize packets in the compressed stream, so we
+  // prefix a proto preamble to each packet. The compressed stream looks like a
+  // valid Trace proto.
+  Preamble preamble =
+      GetPreamble<protos::pbzero::Trace::kPacketFieldNumber>(packet.size());
+  PushData(preamble.buf.data(), preamble.size);
+  for (const Slice& slice : packet.slices()) {
+    PushData(slice.start, static_cast<uint32_t>(slice.size));
+  }
+}
+
+void ZlibPacketCompressor::PushData(const void* data, uint32_t size) {
+  stream_.next_in = const_cast<Bytef*>(static_cast<const Bytef*>(data));
+  stream_.avail_in = static_cast<uInt>(size);
+  while (stream_.avail_in != 0) {
+    if (stream_.avail_out == 0) {
+      NewOutputSlice();
+    }
+    int status = deflate(&stream_, Z_NO_FLUSH);
+    PERFETTO_CHECK(status == Z_OK);
+  }
+}
+
+TracePacket ZlibPacketCompressor::Finish() {
+  for (;;) {
+    int status = deflate(&stream_, Z_FINISH);
+    if (status == Z_STREAM_END)
+      break;
+    PERFETTO_CHECK(status == Z_OK || status == Z_BUF_ERROR);
+    NewOutputSlice();
+  }
+
+  PushCurSlice();
+
+  TracePacket packet;
+  packet.AddSlice(PreambleToSlice(
+      GetPreamble<protos::pbzero::TracePacket::kCompressedPacketsFieldNumber>(
+          total_new_slices_size_)));
+  for (auto& slice : new_slices_) {
+    packet.AddSlice(std::move(slice));
+  }
+  return packet;
+}
+
+void ZlibPacketCompressor::NewOutputSlice() {
+  PushCurSlice();
+  cur_slice_ = std::make_unique<uint8_t[]>(kZlibCompressSliceSize);
+  stream_.next_out = reinterpret_cast<Bytef*>(cur_slice_.get());
+  stream_.avail_out = kZlibCompressSliceSize;
+}
+
+void ZlibPacketCompressor::PushCurSlice() {
+  if (cur_slice_) {
+    total_new_slices_size_ += kZlibCompressSliceSize - stream_.avail_out;
+    new_slices_.push_back(Slice::TakeOwnership(
+        std::move(cur_slice_), kZlibCompressSliceSize - stream_.avail_out));
+  }
+}
+
+}  // namespace
+
+void ZlibCompressFn(std::vector<TracePacket>* packets) {
+  if (packets->empty()) {
+    return;
+  }
+
+  ZlibPacketCompressor stream;
+
+  for (const TracePacket& packet : *packets) {
+    stream.PushPacket(packet);
+  }
+
+  TracePacket packet = stream.Finish();
+
+  packets->clear();
+  packets->push_back(std::move(packet));
+}
+
+}  // namespace perfetto
diff --git a/src/tracing/core/zlib_compressor.h b/src/tracing/core/zlib_compressor.h
new file mode 100644
index 0000000..1962c48
--- /dev/null
+++ b/src/tracing/core/zlib_compressor.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_TRACING_CORE_ZLIB_COMPRESSOR_H_
+#define SRC_TRACING_CORE_ZLIB_COMPRESSOR_H_
+
+#include <vector>
+
+#include "perfetto/ext/tracing/core/trace_packet.h"
+
+namespace perfetto {
+
+// Matches TracingServiceImpl::kMaxTracePacketSliceSize. Exposed for testing.
+static constexpr size_t kZlibCompressSliceSize = 128 * 1024 - 512;
+
+void ZlibCompressFn(std::vector<TracePacket>*);
+
+}  // namespace perfetto
+
+#endif  // SRC_TRACING_CORE_ZLIB_COMPRESSOR_H_
diff --git a/src/tracing/core/zlib_compressor_unittest.cc b/src/tracing/core/zlib_compressor_unittest.cc
new file mode 100644
index 0000000..2471c1b
--- /dev/null
+++ b/src/tracing/core/zlib_compressor_unittest.cc
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#include "src/tracing/core/zlib_compressor.h"
+
+#include <random>
+
+#include <zlib.h>
+
+#include "protos/perfetto/trace/test_event.gen.h"
+#include "protos/perfetto/trace/trace.gen.h"
+#include "protos/perfetto/trace/trace_packet.gen.h"
+#include "src/tracing/core/tracing_service_impl.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace {
+
+using ::testing::Each;
+using ::testing::ElementsAre;
+using ::testing::Field;
+using ::testing::IsEmpty;
+using ::testing::Le;
+using ::testing::Not;
+using ::testing::Property;
+using ::testing::SizeIs;
+
+template <typename F>
+TracePacket CreateTracePacket(F fill_function) {
+  protos::gen::TracePacket msg;
+  fill_function(&msg);
+  std::vector<uint8_t> buf = msg.SerializeAsArray();
+  Slice slice = Slice::Allocate(buf.size());
+  memcpy(slice.own_data(), buf.data(), buf.size());
+  perfetto::TracePacket packet;
+  packet.AddSlice(std::move(slice));
+  return packet;
+}
+
+// Return a copy of the `old` trace packets that owns its own slices data.
+TracePacket CopyTracePacket(const TracePacket& old) {
+  TracePacket ret;
+  for (const Slice& slice : old.slices()) {
+    auto new_slice = Slice::Allocate(slice.size);
+    memcpy(new_slice.own_data(), slice.start, slice.size);
+    ret.AddSlice(std::move(new_slice));
+  }
+  return ret;
+}
+
+std::vector<TracePacket> CopyTracePackets(const std::vector<TracePacket>& old) {
+  std::vector<TracePacket> ret;
+  ret.reserve(old.size());
+  for (const TracePacket& trace_packet : old) {
+    ret.push_back(CopyTracePacket(trace_packet));
+  }
+  return ret;
+}
+std::string RandomString(size_t size) {
+  std::default_random_engine rnd(0);
+  std::uniform_int_distribution<> dist(0, 255);
+  std::string s;
+  s.resize(size);
+  for (size_t i = 0; i < s.size(); i++)
+    s[i] = static_cast<char>(dist(rnd));
+  return s;
+}
+
+std::string Decompress(const std::string& data) {
+  uint8_t out[1024];
+
+  z_stream stream{};
+  stream.next_in = reinterpret_cast<uint8_t*>(const_cast<char*>(data.data()));
+  stream.avail_in = static_cast<unsigned int>(data.size());
+
+  EXPECT_EQ(inflateInit(&stream), Z_OK);
+  std::string s;
+
+  int ret;
+  do {
+    stream.next_out = out;
+    stream.avail_out = sizeof(out);
+    ret = inflate(&stream, Z_NO_FLUSH);
+    EXPECT_NE(ret, Z_STREAM_ERROR);
+    EXPECT_NE(ret, Z_NEED_DICT);
+    EXPECT_NE(ret, Z_DATA_ERROR);
+    EXPECT_NE(ret, Z_MEM_ERROR);
+    s.append(reinterpret_cast<char*>(out), sizeof(out) - stream.avail_out);
+  } while (ret != Z_STREAM_END);
+
+  inflateEnd(&stream);
+  return s;
+}
+
+static_assert(kZlibCompressSliceSize ==
+              TracingServiceImpl::kMaxTracePacketSliceSize);
+
+TEST(ZlibCompressFnTest, Empty) {
+  std::vector<TracePacket> packets;
+
+  ZlibCompressFn(&packets);
+
+  EXPECT_THAT(packets, IsEmpty());
+}
+
+TEST(ZlibCompressFnTest, End2EndCompressAndDecompress) {
+  std::vector<TracePacket> packets;
+
+  packets.push_back(CreateTracePacket([](protos::gen::TracePacket* msg) {
+    auto* for_testing = msg->mutable_for_testing();
+    for_testing->set_str("abc");
+  }));
+  packets.push_back(CreateTracePacket([](protos::gen::TracePacket* msg) {
+    auto* for_testing = msg->mutable_for_testing();
+    for_testing->set_str("def");
+  }));
+
+  ZlibCompressFn(&packets);
+
+  ASSERT_THAT(packets, SizeIs(1));
+  protos::gen::TracePacket compressed_packet_proto;
+  ASSERT_TRUE(compressed_packet_proto.ParseFromString(
+      packets[0].GetRawBytesForTesting()));
+  const std::string& data = compressed_packet_proto.compressed_packets();
+  EXPECT_THAT(data, Not(IsEmpty()));
+  protos::gen::Trace subtrace;
+  ASSERT_TRUE(subtrace.ParseFromString(Decompress(data)));
+  EXPECT_THAT(
+      subtrace.packet(),
+      ElementsAre(Property(&protos::gen::TracePacket::for_testing,
+                           Property(&protos::gen::TestEvent::str, "abc")),
+                  Property(&protos::gen::TracePacket::for_testing,
+                           Property(&protos::gen::TestEvent::str, "def"))));
+}
+
+TEST(ZlibCompressFnTest, MaxSliceSize) {
+  std::vector<TracePacket> packets;
+
+  constexpr size_t kStopOutputSize =
+      TracingServiceImpl::kMaxTracePacketSliceSize + 2000;
+
+  TracePacket compressed_packet;
+  while (compressed_packet.size() < kStopOutputSize) {
+    packets.push_back(CreateTracePacket([](protos::gen::TracePacket* msg) {
+      auto* for_testing = msg->mutable_for_testing();
+      for_testing->set_str(RandomString(65536));
+    }));
+    {
+      std::vector<TracePacket> packets_copy = CopyTracePackets(packets);
+      ZlibCompressFn(&packets_copy);
+      ASSERT_THAT(packets_copy, SizeIs(1));
+      compressed_packet = std::move(packets_copy[0]);
+    }
+  }
+
+  EXPECT_GE(compressed_packet.slices().size(), 2u);
+  ASSERT_GT(compressed_packet.size(),
+            TracingServiceImpl::kMaxTracePacketSliceSize);
+  EXPECT_THAT(compressed_packet.slices(),
+              Each(Field(&Slice::size,
+                         Le(TracingServiceImpl::kMaxTracePacketSliceSize))));
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/tracing/internal/tracing_muxer_impl.cc b/src/tracing/internal/tracing_muxer_impl.cc
index 181237f..5169a2b 100644
--- a/src/tracing/internal/tracing_muxer_impl.cc
+++ b/src/tracing/internal/tracing_muxer_impl.cc
@@ -644,8 +644,7 @@
 }
 
 void TracingMuxerImpl::ConsumerImpl::OnSessionCloned(
-    bool /*success*/,
-    const std::string& /*error*/) {
+    const OnSessionClonedArgs&) {
   // CloneSession is not exposed in the SDK. This should never happen.
   PERFETTO_DCHECK(false);
 }
@@ -1165,7 +1164,8 @@
         }
         // Only allow certain interceptors for now.
         if (descriptor.name() != "test_interceptor" &&
-            descriptor.name() != "console") {
+            descriptor.name() != "console" &&
+            descriptor.name() != "etwexport") {
           PERFETTO_ELOG(
               "Interceptors are experimental. If you want to use them, please "
               "get in touch with the project maintainers "
diff --git a/src/tracing/internal/tracing_muxer_impl.h b/src/tracing/internal/tracing_muxer_impl.h
index 4c87bb9..54d2fc3 100644
--- a/src/tracing/internal/tracing_muxer_impl.h
+++ b/src/tracing/internal/tracing_muxer_impl.h
@@ -300,7 +300,7 @@
     void OnAttach(bool success, const TraceConfig&) override;
     void OnTraceStats(bool success, const TraceStats&) override;
     void OnObservableEvents(const ObservableEvents&) override;
-    void OnSessionCloned(bool, const std::string&) override;
+    void OnSessionCloned(const OnSessionClonedArgs&) override;
 
     void NotifyStartComplete();
     void NotifyError(const TracingError&);
diff --git a/src/tracing/internal/tracing_muxer_impl_integrationtest.cc b/src/tracing/internal/tracing_muxer_impl_integrationtest.cc
index 5711d73..bc28cde 100644
--- a/src/tracing/internal/tracing_muxer_impl_integrationtest.cc
+++ b/src/tracing/internal/tracing_muxer_impl_integrationtest.cc
@@ -25,6 +25,12 @@
 
 class TracingMuxerImplIntegrationTest : public testing::Test {
  protected:
+  void SetUp() override {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    GTEST_SKIP() << "Unix sockets not supported on windows";
+#endif
+  }
+
   // Sets the environment variable `name` to `value`. Restores it to the
   // previous value when the test finishes.
   void SetEnvVar(const char* name, const char* value) {
@@ -38,7 +44,7 @@
     base::SetEnv(name, value);
   }
 
-  ~TracingMuxerImplIntegrationTest() {
+  ~TracingMuxerImplIntegrationTest() override {
     perfetto::Tracing::ResetForTesting();
     while (!prev_state_.empty()) {
       const EnvVar& var = prev_state_.top();
diff --git a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
index 7eecd09..cf34361 100644
--- a/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
+++ b/src/tracing/ipc/consumer/consumer_ipc_client_impl.cc
@@ -479,10 +479,11 @@
           // If the IPC fails, we are talking to an older version of the service
           // that didn't support CloneSession at all.
           weak_this->consumer_->OnSessionCloned(
-              false, "CloneSession IPC not supported");
+              {false, "CloneSession IPC not supported", {}});
         } else {
-          weak_this->consumer_->OnSessionCloned(response->success(),
-                                                response->error());
+          base::Uuid uuid(response->uuid_lsb(), response->uuid_msb());
+          weak_this->consumer_->OnSessionCloned(
+              {response->success(), response->error(), uuid});
         }
       });
   consumer_port_.CloneSession(req, std::move(async_response));
diff --git a/src/tracing/ipc/service/consumer_ipc_service.cc b/src/tracing/ipc/service/consumer_ipc_service.cc
index 2f45713..ac52864 100644
--- a/src/tracing/ipc/service/consumer_ipc_service.cc
+++ b/src/tracing/ipc/service/consumer_ipc_service.cc
@@ -480,14 +480,15 @@
 }
 
 void ConsumerIPCService::RemoteConsumer::OnSessionCloned(
-    bool success,
-    const std::string& error) {
+    const OnSessionClonedArgs& args) {
   if (!clone_session_response.IsBound())
     return;
 
   auto resp = ipc::AsyncResult<protos::gen::CloneSessionResponse>::Create();
-  resp->set_success(success);
-  resp->set_error(error);
+  resp->set_success(args.success);
+  resp->set_error(args.error);
+  resp->set_uuid_msb(args.uuid.msb());
+  resp->set_uuid_lsb(args.uuid.lsb());
   std::move(clone_session_response).Resolve(std::move(resp));
 }
 
diff --git a/src/tracing/ipc/service/consumer_ipc_service.h b/src/tracing/ipc/service/consumer_ipc_service.h
index d570c51..f1424d9 100644
--- a/src/tracing/ipc/service/consumer_ipc_service.h
+++ b/src/tracing/ipc/service/consumer_ipc_service.h
@@ -94,7 +94,7 @@
     void OnAttach(bool, const TraceConfig&) override;
     void OnTraceStats(bool, const TraceStats&) override;
     void OnObservableEvents(const ObservableEvents&) override;
-    void OnSessionCloned(bool, const std::string&) override;
+    void OnSessionCloned(const OnSessionClonedArgs&) override;
 
     void CloseObserveEventsResponseStream();
 
diff --git a/src/tracing/ipc/service/service_ipc_host_impl.cc b/src/tracing/ipc/service/service_ipc_host_impl.cc
index 85029a2..1df674e 100644
--- a/src/tracing/ipc/service/service_ipc_host_impl.cc
+++ b/src/tracing/ipc/service/service_ipc_host_impl.cc
@@ -40,12 +40,15 @@
 // Implements the publicly exposed factory method declared in
 // include/tracing/posix_ipc/posix_service_host.h.
 std::unique_ptr<ServiceIPCHost> ServiceIPCHost::CreateInstance(
-    base::TaskRunner* task_runner) {
-  return std::unique_ptr<ServiceIPCHost>(new ServiceIPCHostImpl(task_runner));
+    base::TaskRunner* task_runner,
+    TracingService::InitOpts init_opts) {
+  return std::unique_ptr<ServiceIPCHost>(
+      new ServiceIPCHostImpl(task_runner, init_opts));
 }
 
-ServiceIPCHostImpl::ServiceIPCHostImpl(base::TaskRunner* task_runner)
-    : task_runner_(task_runner) {}
+ServiceIPCHostImpl::ServiceIPCHostImpl(base::TaskRunner* task_runner,
+                                       TracingService::InitOpts init_opts)
+    : task_runner_(task_runner), init_opts_(init_opts) {}
 
 ServiceIPCHostImpl::~ServiceIPCHostImpl() {}
 
@@ -95,7 +98,8 @@
   std::unique_ptr<SharedMemory::Factory> shm_factory(
       new PosixSharedMemory::Factory());
 #endif
-  svc_ = TracingService::CreateInstance(std::move(shm_factory), task_runner_);
+  svc_ = TracingService::CreateInstance(std::move(shm_factory), task_runner_,
+                                        init_opts_);
 
   if (!producer_ipc_port_ || !consumer_ipc_port_) {
     Shutdown();
diff --git a/src/tracing/ipc/service/service_ipc_host_impl.h b/src/tracing/ipc/service/service_ipc_host_impl.h
index dda7e5b..4ccfa65 100644
--- a/src/tracing/ipc/service/service_ipc_host_impl.h
+++ b/src/tracing/ipc/service/service_ipc_host_impl.h
@@ -33,7 +33,8 @@
 // producer_ipc_service.cc and consumer_ipc_service.cc.
 class ServiceIPCHostImpl : public ServiceIPCHost {
  public:
-  ServiceIPCHostImpl(base::TaskRunner*);
+  explicit ServiceIPCHostImpl(base::TaskRunner*,
+                              TracingService::InitOpts init_opts = {});
   ~ServiceIPCHostImpl() override;
 
   // ServiceIPCHost implementation.
@@ -51,6 +52,7 @@
   void Shutdown();
 
   base::TaskRunner* const task_runner_;
+  const TracingService::InitOpts init_opts_;
   std::unique_ptr<TracingService> svc_;  // The service business logic.
 
   // The IPC host that listens on the Producer socket. It owns the
diff --git a/src/tracing/test/aligned_buffer_test.cc b/src/tracing/test/aligned_buffer_test.cc
index 082e130..fc9b8bc 100644
--- a/src/tracing/test/aligned_buffer_test.cc
+++ b/src/tracing/test/aligned_buffer_test.cc
@@ -20,11 +20,6 @@
 
 namespace perfetto {
 
-#if !PERFETTO_IS_AT_LEAST_CPP17()
-// static
-constexpr size_t AlignedBufferTest::kNumPages;
-#endif
-
 void AlignedBufferTest::SetUp() {
   page_size_ = GetParam();
   buf_.reset(new TestSharedMemory(page_size_ * kNumPages));
diff --git a/src/tracing/test/mock_consumer.h b/src/tracing/test/mock_consumer.h
index 3b6ad2b..2253d93 100644
--- a/src/tracing/test/mock_consumer.h
+++ b/src/tracing/test/mock_consumer.h
@@ -83,7 +83,7 @@
   MOCK_METHOD(void, OnAttach, (bool, const TraceConfig&), (override));
   MOCK_METHOD(void, OnTraceStats, (bool, const TraceStats&), (override));
   MOCK_METHOD(void, OnObservableEvents, (const ObservableEvents&), (override));
-  MOCK_METHOD(void, OnSessionCloned, (bool, const std::string&), (override));
+  MOCK_METHOD(void, OnSessionCloned, (const OnSessionClonedArgs&), (override));
 
   // gtest doesn't support move-only types. This wrapper is here jut to pass
   // a pointer to the vector (rather than the vector itself) to the mock method.
diff --git a/src/tracing/test/tracing_integration_test.cc b/src/tracing/test/tracing_integration_test.cc
index 05fa126..29949a8 100644
--- a/src/tracing/test/tracing_integration_test.cc
+++ b/src/tracing/test/tracing_integration_test.cc
@@ -97,7 +97,7 @@
   MOCK_METHOD(void, OnAttach, (bool, const TraceConfig&), (override));
   MOCK_METHOD(void, OnTraceStats, (bool, const TraceStats&), (override));
   MOCK_METHOD(void, OnObservableEvents, (const ObservableEvents&), (override));
-  MOCK_METHOD(void, OnSessionCloned, (bool, const std::string&), (override));
+  MOCK_METHOD(void, OnSessionCloned, (const OnSessionClonedArgs&), (override));
 
   // Workaround, gmock doesn't support yet move-only types, passing a pointer.
   void OnTraceData(std::vector<TracePacket> packets, bool has_more) {
diff --git a/test/cmdline_integrationtest.cc b/test/cmdline_integrationtest.cc
index cbc3555..f8c7a43 100644
--- a/test/cmdline_integrationtest.cc
+++ b/test/cmdline_integrationtest.cc
@@ -46,6 +46,7 @@
 using ::testing::ContainsRegex;
 using ::testing::Each;
 using ::testing::ElementsAreArray;
+using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Property;
 using ::testing::SizeIs;
@@ -80,6 +81,13 @@
   return trace_config;
 }
 
+class ScopedFileRemove {
+ public:
+  explicit ScopedFileRemove(const std::string& path) : path_(path) {}
+  ~ScopedFileRemove() { remove(path_.c_str()); }
+  std::string path_;
+};
+
 class PerfettoCmdlineTest : public ::testing::Test {
  public:
   void SetUp() override {
@@ -137,8 +145,10 @@
   // This is in common to the 3 TEST_F SaveForBugreport* fixtures, which differ
   // only in the config, passed here as input.
   void RunBugreportTest(protos::gen::TraceConfig trace_config,
-                        bool check_original_trace = true) {
+                        bool check_original_trace = true,
+                        bool use_explicit_clone = false) {
     const std::string path = RandomTraceFileName();
+    ScopedFileRemove remove_on_test_exit(path);
 
     auto perfetto_proc = ExecPerfetto(
         {
@@ -149,9 +159,10 @@
         },
         trace_config.SerializeAsString());
 
-    auto perfetto_br_proc = ExecPerfetto({
-        "--save-for-bugreport",
-    });
+    Exec perfetto_br_proc =
+        use_explicit_clone
+            ? ExecPerfetto({"--out", GetBugreportTracePath(), "--clone", "-1"})
+            : ExecPerfetto({"--save-for-bugreport"});
 
     // Start the service and connect a simple fake producer.
     StartServiceIfRequiredNoNewExecsAfterThis();
@@ -364,6 +375,7 @@
   // (could deadlock) to fork after we've spawned some threads which might
   // printf (and thus hold locks).
   const std::string path = RandomTraceFileName();
+  ScopedFileRemove remove_on_test_exit(path);
   auto perfetto_proc = ExecPerfetto(
       {
           "-o",
@@ -458,6 +470,7 @@
   // (could deadlock) to fork after we've spawned some threads which might
   // printf (and thus hold locks).
   const std::string path = RandomTraceFileName();
+  ScopedFileRemove remove_on_test_exit(path);
   auto perfetto_proc = ExecPerfetto(
       {
           "-o",
@@ -562,6 +575,7 @@
   // (could deadlock) to fork after we've spawned some threads which might
   // printf (and thus hold locks).
   const std::string path = RandomTraceFileName();
+  ScopedFileRemove remove_on_test_exit(path);
   auto perfetto_proc = ExecPerfetto(
       {
           "--dropbox",
@@ -616,6 +630,7 @@
   // (could deadlock) to fork after we've spawned some threads which might
   // printf (and thus hold locks).
   const std::string path = RandomTraceFileName();
+  ScopedFileRemove remove_on_test_exit(path);
   auto perfetto_proc = ExecPerfetto(
       {
           "-o",
@@ -719,6 +734,7 @@
   // (could deadlock) to fork after we've spawned some threads which might
   // printf (and thus hold locks).
   const std::string path = RandomTraceFileName();
+  ScopedFileRemove remove_on_test_exit(path);
   std::string triggers = R"(
     activate_triggers: "trigger_name_2"
     activate_triggers: "trigger_name"
@@ -782,6 +798,7 @@
   // (could deadlock) to fork after we've spawned some threads which might
   // printf (and thus hold locks).
   const std::string path = RandomTraceFileName();
+  ScopedFileRemove remove_on_test_exit(path);
   auto perfetto_proc = ExecPerfetto(
       {
           "-o",
@@ -829,11 +846,100 @@
   protos::gen::Trace trace;
   ASSERT_TRUE(trace.ParseFromString(trace_str));
   EXPECT_LT(static_cast<int>(kMessageCount), trace.packet_size());
-  for (const auto& packet : trace.packet()) {
-    if (packet.has_trigger()) {
-      EXPECT_EQ("trigger_name", packet.trigger().trigger_name());
-    }
+  EXPECT_THAT(trace.packet(),
+              Contains(Property(&protos::gen::TracePacket::trigger,
+                                Property(&protos::gen::Trigger::trigger_name,
+                                         Eq("trigger_name")))));
+}
+
+TEST_F(PerfettoCmdlineTest, TriggerCloneSnapshot) {
+  constexpr size_t kMessageCount = 2;
+  constexpr size_t kMessageSize = 2;
+  protos::gen::TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(1024);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.perfetto.FakeProducer");
+  ds_config->mutable_for_testing()->set_message_count(kMessageCount);
+  ds_config->mutable_for_testing()->set_message_size(kMessageSize);
+  auto* trigger_cfg = trace_config.mutable_trigger_config();
+  trigger_cfg->set_trigger_mode(
+      protos::gen::TraceConfig::TriggerConfig::CLONE_SNAPSHOT);
+  trigger_cfg->set_trigger_timeout_ms(600000);
+  auto* trigger = trigger_cfg->add_triggers();
+  trigger->set_name("trigger_name");
+  // |stop_delay_ms| must be long enough that we can write the packets in
+  // before the trace finishes. This has to be long enough for the slowest
+  // emulator. But as short as possible to prevent the test running a long
+  // time.
+  trigger->set_stop_delay_ms(500);
+
+  // We have to construct all the processes we want to fork before we start the
+  // service with |StartServiceIfRequired()|. this is because it is unsafe
+  // (could deadlock) to fork after we've spawned some threads which might
+  // printf (and thus hold locks).
+  const std::string path = RandomTraceFileName();
+  ScopedFileRemove remove_on_test_exit(path);
+  auto perfetto_proc = ExecPerfetto(
+      {
+          "-o",
+          path,
+          "-c",
+          "-",
+      },
+      trace_config.SerializeAsString());
+
+  std::string triggers = R"(
+    activate_triggers: "trigger_name"
+  )";
+  auto trigger_proc = ExecPerfetto(
+      {
+          "-c",
+          "-",
+          "--txt",
+      },
+      triggers);
+
+  // Start the service and connect a simple fake producer.
+  StartServiceIfRequiredNoNewExecsAfterThis();
+  auto* fake_producer = ConnectFakeProducer();
+  EXPECT_TRUE(fake_producer);
+
+  std::thread background_trace([&perfetto_proc]() {
+    std::string stderr_str;
+    EXPECT_EQ(0, perfetto_proc.Run(&stderr_str)) << stderr_str;
+  });
+
+  WaitForProducerEnabled();
+  // Wait for the producer to start, and then write out 11 packets, before the
+  // trace actually starts (the trigger is seen).
+  auto on_data_written = task_runner_.CreateCheckpoint("data_written_1");
+  fake_producer->ProduceEventBatch(WrapTask(on_data_written));
+  task_runner_.RunUntilCheckpoint("data_written_1");
+
+  EXPECT_EQ(0, trigger_proc.Run(&stderr_)) << "stderr: " << stderr_;
+
+  // Now we need to wait that the `perfetto_proc` creates the snapshot trace
+  // file in the trace/path.0 file (appending .0). Once that is done we can
+  // kill the perfetto cmd (otherwise it will keep running for the whole
+  // trigger_timeout_ms, unlike the case of STOP_TRACING.
+  std::string snapshot_path = path + ".0";
+  for (int i = 0; i < 100 && !base::FileExists(snapshot_path); i++) {
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
   }
+  ASSERT_TRUE(base::FileExists(snapshot_path));
+
+  perfetto_proc.SendSigterm();
+  background_trace.join();
+
+  std::string trace_str;
+  base::ReadFile(snapshot_path, &trace_str);
+  protos::gen::Trace trace;
+  ASSERT_TRUE(trace.ParseFromString(trace_str));
+  EXPECT_LT(static_cast<int>(kMessageCount), trace.packet_size());
+  EXPECT_THAT(trace.packet(),
+              Contains(Property(&protos::gen::TracePacket::trigger,
+                                Property(&protos::gen::Trigger::trigger_name,
+                                         Eq("trigger_name")))));
 }
 
 TEST_F(PerfettoCmdlineTest, SaveForBugreport) {
@@ -848,6 +954,21 @@
   RunBugreportTest(std::move(trace_config));
 }
 
+TEST_F(PerfettoCmdlineTest, Clone) {
+  TraceConfig trace_config = CreateTraceConfigForBugreportTest();
+  RunBugreportTest(std::move(trace_config), /*check_original_trace=*/true,
+                   /*use_explicit_clone=*/true);
+}
+
+// Regression test for b/279753347 .
+TEST_F(PerfettoCmdlineTest, UnavailableBugreportLeavesNoEmptyFiles) {
+  ScopedFileRemove remove_on_test_exit(GetBugreportTracePath());
+  Exec perfetto_br_proc = ExecPerfetto({"--save-for-bugreport"});
+  StartServiceIfRequiredNoNewExecsAfterThis();
+  perfetto_br_proc.Run(&stderr_);
+  ASSERT_FALSE(base::FileExists(GetBugreportTracePath()));
+}
+
 // Tests that SaveTraceForBugreport() works also if the trace has triggers
 // defined and those triggers have not been hit. This is a regression test for
 // b/188008375 .
diff --git a/test/configs/BUILD.gn b/test/configs/BUILD.gn
index 230fe01..b56822b 100644
--- a/test/configs/BUILD.gn
+++ b/test/configs/BUILD.gn
@@ -40,6 +40,7 @@
     "long_trace.cfg",
     "mm_events.cfg",
     "scheduling.cfg",
+    "snapshot.cfg",
     "summary.cfg",
     "sys_stats.cfg",
     "thermal.cfg",
diff --git a/test/configs/snapshot.cfg b/test/configs/snapshot.cfg
new file mode 100644
index 0000000..eb2c197
--- /dev/null
+++ b/test/configs/snapshot.cfg
@@ -0,0 +1,49 @@
+unique_session_name: "test_snap"
+
+buffers {
+  size_kb: 32768
+  fill_policy: RING_BUFFER
+}
+
+# Enable various data sources as usual.
+data_sources {
+  config {
+    name: "linux.ftrace"
+    target_buffer: 0
+    ftrace_config {
+      ftrace_events: "cpu_frequency"
+      ftrace_events: "cpu_idle"
+      ftrace_events: "sched_process_exec"
+      ftrace_events: "sched_process_exit"
+      ftrace_events: "sched_process_fork"
+      ftrace_events: "sched_process_free"
+      ftrace_events: "sched_process_hang"
+      ftrace_events: "sched_process_wait"
+      ftrace_events: "sched_switch"
+      ftrace_events: "sched_wakeup_new"
+      ftrace_events: "sched_wakeup"
+      ftrace_events: "sched_waking"
+      ftrace_events: "task_newtask"
+      ftrace_events: "task_rename"
+      ftrace_events: "tracing_mark_write"
+    }
+  }
+}
+
+data_sources {
+  config {
+    name: "linux.process_stats"
+    target_buffer: 0
+  }
+}
+
+
+trigger_config {
+  trigger_mode: STOP_TRACING
+  use_clone_snapshot_if_available: true
+  trigger_timeout_ms: 300000
+  triggers {
+    name: "xxx"
+    stop_delay_ms: 0
+  }
+}
diff --git a/test/configs/statsd.cfg b/test/configs/statsd.cfg
index a384b57..10d5b0d 100644
--- a/test/configs/statsd.cfg
+++ b/test/configs/statsd.cfg
@@ -5,7 +5,7 @@
 
 data_sources {
   config {
-    name: "android.statsd"
+    name: "android.statsd_binder"
     target_buffer: 0
     statsd_tracing_config {
       push_atom_id: ATOM_FLASHLIGHT_STATE_CHANGED
diff --git a/test/data/fuchsia_events_and_args.fxt.sha256 b/test/data/fuchsia_events_and_args.fxt.sha256
new file mode 100644
index 0000000..cf71b67
--- /dev/null
+++ b/test/data/fuchsia_events_and_args.fxt.sha256
@@ -0,0 +1 @@
+1597b8fd935caedb1e319e423cff4dbfba68327cad6ec21f985c0378c2c76b20
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
index 62feb86..adfa483 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_expand_camera.png.sha256
@@ -1 +1 @@
-826cb6e532e4d342e02b6022917423bb481a3b8764bb697706ca890d0ad847c3
\ No newline at end of file
+37ce86e92a72fe05c24acfcadf1393f3407a99e3dcdda6b3c883f380ffd00b41
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256 b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
index de95b69..ba42f34 100644
--- a/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
+++ b/test/data/ui-screenshots/ui-android_trace_30s_load.png.sha256
@@ -1 +1 @@
-a56f7bcdebf30ae7da87417e1951867446ae7bd337e450d52ffa45d5d4c2ab65
\ No newline at end of file
+0c7f3705f1d29c0b16756b66cb748635352fef3928a173b34d995356e6d3d58e
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
index d34a6fd..4c65000 100644
--- a/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_missing_track_names_load.png.sha256
@@ -1 +1 @@
-e97284d62ddefdbd091e5e820881e81d5ad95a22edcf50d83cacc06b7b9bedde
\ No newline at end of file
+c8277a777519e7c1bf762d4e0e299a79a66da88ce54ed26e6d27939dca6128c9
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
index 5780168..f504faf 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_expand_browser_proc.png.sha256
@@ -1 +1 @@
-eb2d68c1fab251b50184431abb3a10b420f95e5eca2dc341028feb4a3ea03d40
\ No newline at end of file
+dc246b0bf68834a63464c0c98e35a060cb3698947084cdb9d976207a37bf8156
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256 b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
index 73891dc..0ee6b17 100644
--- a/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
+++ b/test/data/ui-screenshots/ui-chrome_rendering_desktop_load.png.sha256
@@ -1 +1 @@
-fd17d4de0cae0cfbaaed535519b333d7ee13cac65ad14144e68335c8ca521216
\ No newline at end of file
+e79c53f02bed0a629c76fd3d044492dc8e619fe9f58c597db5f4417cbe91f805
\ 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 d657179..0e50d9b 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 @@
-92aa98e5edad0293244bc0a94672a2e9b63e9b6aaa86ab0a58db8b106e912693
\ No newline at end of file
+cc543d6db95f56863f5d6c90dbbb8b07e8dff539534c53d595b1faee807903da
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256
index 69e55e1..bf8f89b 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_dismiss_1.png.sha256
@@ -1 +1 @@
-e7dc92a76eec326637bebaa176482197c621f48bc066fc761d0b1d19513c8c21
\ No newline at end of file
+db689629c3ba9a51e74d48edd3e801bf062dd985ed5210e5a9b4f1bce50f29a7
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256
index 48395a1..6c30778 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_dismiss_2.png.sha256
@@ -1 +1 @@
-36628de5b3af4cb6de10e6b4f1860ea8534b8b0db37ee994fddc94947947cb6e
\ No newline at end of file
+eae8d6c700a16b5062736c54e4fe0ffab569ffd839715138e74cd257fdb014fa
\ 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 623260e..5117a65 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 @@
-b51e0a5035eab217bc4339800e8a6c4025bfc7d5240844c152fbc867b28f75ba
\ No newline at end of file
+5767e81834101bf14dcd1917544f1605b8dbb8aa747a7eefd1c52f4db891575f
\ 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 679bd39..ff3aafa 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 @@
-99d3e463fe3812942825829a388bb2d5b11d73010c3213b11d09884dfc1663b5
\ No newline at end of file
+ab1a1b7c948e008fa5d44a4471dc24977f3ab2d592c1ceeaa0ab4afac5bb2336
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256 b/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256
index e84c777..f4e56d2 100644
--- a/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256
+++ b/test/data/ui-screenshots/ui-modal_dialog_switch_page_no_dialog.png.sha256
@@ -1 +1 @@
-c5c11d6515e27365eeaf50602243d918cc688ee18d1390649c786b28225e1d81
\ No newline at end of file
+d7c89c766d8db408de06f79654e2f81d8c761c08c4451991447807468df0f3d5
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
index 849be2d..46f0129 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_navigate_back_and_forward.png.sha256
@@ -1 +1 @@
-4b07a28b92a72568615003beb4eb086adc9be581a27baa2ea593c5e88802647b
\ No newline at end of file
+e596f8037a578f1e58a33bbe08f5d5621b1d37e55456409e5f8799a6897eedb9
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
index eb26087..23be44d 100644
--- a/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_navigate_open_trace_from_url.png.sha256
@@ -1 +1 @@
-9bac90702af629b8ecec00b99d86704f188b332695dea2eaf49d79aba3fc8670
\ No newline at end of file
+b938d4dfcc109165feaedb9421276a355d03a383a0d21ab91ff72409b18a3ab5
\ 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 b3581ce..b3b1619 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 @@
-f64d01ccda59ec74395e00267d7adbf179f5dd962affeeef639ae583756bc65b
\ No newline at end of file
+ae78e1f372a6a052a650974464f2e706ab8cb665c639ca4b0b15b48dce95d185
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256 b/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256
index 2afc1a3..0abbaa7 100644
--- a/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_trace_and_go_back_to_landing_page.png.sha256
@@ -1 +1 @@
-04e0762424dae233fb13d5cfff51a2d3075ad62220538010d0ab376fc79022a9
\ No newline at end of file
+d62fb2e2f4067552d4f9d4170b4ffa45c74171b4de750292ded957cb6b12b669
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
index 4cfdf9f..9287876 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_access_subpage_then_go_back.png.sha256
@@ -1 +1 @@
-3539c6d2a8ac9d9e15455be491aa171f75f00fdb39e36971b50a060d70732e81
\ No newline at end of file
+30faf223bfc57acbbcca2308c716064d3447dee2673db6329ad9188bf08a59e6
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
index eb26087..23be44d 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_first_trace_from_url.png.sha256
@@ -1 +1 @@
-9bac90702af629b8ecec00b99d86704f188b332695dea2eaf49d79aba3fc8670
\ No newline at end of file
+b938d4dfcc109165feaedb9421276a355d03a383a0d21ab91ff72409b18a3ab5
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256 b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
index 4cfdf9f..9287876 100644
--- a/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_open_two_traces_then_go_back_open_second_trace_from_url.png.sha256
@@ -1 +1 @@
-3539c6d2a8ac9d9e15455be491aa171f75f00fdb39e36971b50a060d70732e81
\ No newline at end of file
+30faf223bfc57acbbcca2308c716064d3447dee2673db6329ad9188bf08a59e6
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
index d8da346..9287876 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_back_to_first_trace.png.sha256
@@ -1 +1 @@
-150bfec1eb606a548dc39bbdf081efb82a529b7cc39500605c4e56a5d742fea6
\ No newline at end of file
+30faf223bfc57acbbcca2308c716064d3447dee2673db6329ad9188bf08a59e6
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256
index a494f13..4324721 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_go_to_page_with_no_trace.png.sha256
@@ -1 +1 @@
-8de36401497e509ab127d202b83a056c40c2f5f461254f1ea79fe7a4861b059e
\ No newline at end of file
+9d502e8458f85884c7bf1a6411aa3425c63ccf102e6f537318733704e7a416fb
\ 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 6d882f6..535b9b1 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 @@
-3898ba5cd5fdb63fdef0abced3a1174f5ee864c9b01c7e033e7e2f5e1f0280f2
\ No newline at end of file
+b0d5928fbcf0b7adfc06386ea1961d301ee19f66c61b190f9d00fd5acef824c0
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
index eb26087..23be44d 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_second_trace.png.sha256
@@ -1 +1 @@
-9bac90702af629b8ecec00b99d86704f188b332695dea2eaf49d79aba3fc8670
\ No newline at end of file
+b938d4dfcc109165feaedb9421276a355d03a383a0d21ab91ff72409b18a3ab5
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
index 4cfdf9f..9287876 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_open_trace_.png.sha256
@@ -1 +1 @@
-3539c6d2a8ac9d9e15455be491aa171f75f00fdb39e36971b50a060d70732e81
\ No newline at end of file
+30faf223bfc57acbbcca2308c716064d3447dee2673db6329ad9188bf08a59e6
\ No newline at end of file
diff --git a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256 b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
index 4cfdf9f..9287876 100644
--- a/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
+++ b/test/data/ui-screenshots/ui-routing_start_from_no_trace_refresh.png.sha256
@@ -1 +1 @@
-3539c6d2a8ac9d9e15455be491aa171f75f00fdb39e36971b50a060d70732e81
\ No newline at end of file
+30faf223bfc57acbbcca2308c716064d3447dee2673db6329ad9188bf08a59e6
\ No newline at end of file
diff --git a/test/end_to_end_benchmark.cc b/test/end_to_end_benchmark.cc
index ac7f8fa..ce63780 100644
--- a/test/end_to_end_benchmark.cc
+++ b/test/end_to_end_benchmark.cc
@@ -232,7 +232,7 @@
 
 void ConstantRateProducerArgs(benchmark::internal::Benchmark* b) {
   int message_count = IsBenchmarkFunctionalOnly() ? 2 * 1024 : 128 * 1024;
-  int min_speed = IsBenchmarkFunctionalOnly() ? 64 : 8;
+  int min_speed = IsBenchmarkFunctionalOnly() ? 128 : 8;
   int max_speed = 128;
   for (int speed = min_speed; speed <= max_speed; speed *= 2) {
     b->Args({message_count, 128, speed});
@@ -242,7 +242,7 @@
 
 void SaturateCpuConsumerArgs(benchmark::internal::Benchmark* b) {
   int min_payload = 8;
-  int max_payload = IsBenchmarkFunctionalOnly() ? 16 : 64 * 1024;
+  int max_payload = IsBenchmarkFunctionalOnly() ? 8 : 64 * 1024;
   for (int bytes = min_payload; bytes <= max_payload; bytes *= 2) {
     b->Args({bytes, 0 /* speed */});
   }
diff --git a/test/ftrace_integrationtest.cc b/test/ftrace_integrationtest.cc
index 318b308..ae1b104 100644
--- a/test/ftrace_integrationtest.cc
+++ b/test/ftrace_integrationtest.cc
@@ -244,7 +244,7 @@
   helper.WaitForConsumerConnect();
 
   TraceConfig trace_config;
-  trace_config.add_buffers()->set_size_kb(1024);
+  trace_config.add_buffers()->set_size_kb(64);
 
   auto* ds_config = trace_config.add_data_sources()->mutable_config();
   ds_config->set_name("linux.ftrace");
diff --git a/test/test_helper.cc b/test/test_helper.cc
index cbc3aca..49a9961 100644
--- a/test/test_helper.cc
+++ b/test/test_helper.cc
@@ -290,7 +290,7 @@
 
 void TestHelper::OnObservableEvents(const ObservableEvents&) {}
 
-void TestHelper::OnSessionCloned(bool, const std::string&) {}
+void TestHelper::OnSessionCloned(const OnSessionClonedArgs&) {}
 
 // static
 const char* TestHelper::GetDefaultModeConsumerSocketName() {
diff --git a/test/test_helper.h b/test/test_helper.h
index dd18b4c..2cabd98 100644
--- a/test/test_helper.h
+++ b/test/test_helper.h
@@ -290,7 +290,7 @@
   void OnAttach(bool, const TraceConfig&) override;
   void OnTraceStats(bool, const TraceStats&) override;
   void OnObservableEvents(const ObservableEvents&) override;
-  void OnSessionCloned(bool, const std::string&) override;
+  void OnSessionCloned(const OnSessionClonedArgs&) override;
 
   // Starts the tracing service if in kStartDaemons mode.
   void StartServiceIfRequired();
diff --git a/test/trace_processor/diff_tests/android/android_battery_stats_event_slices.out b/test/trace_processor/diff_tests/android/android_battery_stats_event_slices.out
new file mode 100644
index 0000000..82cd36c
--- /dev/null
+++ b/test/trace_processor/diff_tests/android/android_battery_stats_event_slices.out
@@ -0,0 +1,4 @@
+"ts","dur","track_name","str_value","int_value"
+1000,8000,"battery_stats.top","mail",123
+3000,-1,"battery_stats.job","mail_job",456
+1000,3000,"battery_stats.job","video_job",789
diff --git a/test/trace_processor/diff_tests/android/android_battery_stats_state.out b/test/trace_processor/diff_tests/android/android_battery_stats_state.out
new file mode 100644
index 0000000..04ea953
--- /dev/null
+++ b/test/trace_processor/diff_tests/android/android_battery_stats_state.out
@@ -0,0 +1,4 @@
+"ts","track_name","value","value_name","dur"
+1000,"battery_stats.audio",1,"active",-1
+1000,"battery_stats.data_conn",13,"lte",3000
+4000,"battery_stats.data_conn",20,"nr",-1
diff --git a/test/trace_processor/diff_tests/android/android_binder_metric.out b/test/trace_processor/diff_tests/android/android_binder_metric.out
index 8191521..09dc248 100644
--- a/test/trace_processor/diff_tests/android/android_binder_metric.out
+++ b/test/trace_processor/diff_tests/android/android_binder_metric.out
@@ -234,11 +234,13 @@
     client_ts: 25827352153
     client_dur: 86322
     client_tid: 422
+    client_pid: 415
     server_process: "system_server"
     server_thread: "binder:641_5"
     server_ts: 25827417672
     server_dur: 11316
     server_tid: 1600
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -271,11 +273,13 @@
     client_ts: 25827531554
     client_dur: 41057
     client_tid: 422
+    client_pid: 415
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25827545050
     server_dur: 18590
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -308,11 +312,13 @@
     client_ts: 25927698833
     client_dur: 43619
     client_tid: 422
+    client_pid: 415
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25927713489
     server_dur: 18478
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -345,11 +351,13 @@
     client_ts: 26027844321
     client_dur: 76502
     client_tid: 422
+    client_pid: 415
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 26027869620
     server_dur: 38328
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -382,11 +390,13 @@
     client_ts: 26128022950
     client_dur: 93620
     client_tid: 422
+    client_pid: 415
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 26128060441
     server_dur: 42549
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -419,11 +429,13 @@
     client_ts: 26228226807
     client_dur: 92197
     client_tid: 422
+    client_pid: 415
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 26228257962
     server_dur: 47691
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -456,11 +468,13 @@
     client_ts: 26328427074
     client_dur: 88516
     client_tid: 422
+    client_pid: 415
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 26328457296
     server_dur: 45253
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -493,11 +507,13 @@
     client_ts: 21625430256
     client_dur: 106084
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21625451288
     server_dur: 25329
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -530,11 +546,13 @@
     client_ts: 21651104412
     client_dur: 1553854
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21651127883
     server_dur: 24255
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -567,11 +585,13 @@
     client_ts: 21681078075
     client_dur: 6957581
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21681097331
     server_dur: 20398
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -604,11 +624,13 @@
     client_ts: 21713184564
     client_dur: 1238385
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21713209886
     server_dur: 8733
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -641,11 +663,13 @@
     client_ts: 21745330426
     client_dur: 2415345
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21745354167
     server_dur: 24535
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -678,11 +702,13 @@
     client_ts: 21772841582
     client_dur: 60073
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21772864934
     server_dur: 21372
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -715,11 +741,13 @@
     client_ts: 21797991492
     client_dur: 58946
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21798015547
     server_dur: 20885
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -752,11 +780,13 @@
     client_ts: 21823141587
     client_dur: 61370
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21823167028
     server_dur: 21656
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -789,11 +819,13 @@
     client_ts: 21848290804
     client_dur: 60709
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21848316259
     server_dur: 20391
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -826,11 +858,13 @@
     client_ts: 21873440775
     client_dur: 55934
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21873461764
     server_dur: 21136
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -863,11 +897,13 @@
     client_ts: 21898589558
     client_dur: 58875
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21898611789
     server_dur: 20494
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -900,11 +936,13 @@
     client_ts: 21923738957
     client_dur: 64169
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21923761253
     server_dur: 25784
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -937,11 +975,13 @@
     client_ts: 21948891420
     client_dur: 64699
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21948920277
     server_dur: 20540
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -974,11 +1014,13 @@
     client_ts: 21974296208
     client_dur: 104989
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21974341636
     server_dur: 25187
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1011,11 +1053,13 @@
     client_ts: 21999539011
     client_dur: 71202
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21999568501
     server_dur: 20357
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1048,11 +1092,13 @@
     client_ts: 22024711257
     client_dur: 328183
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22024736108
     server_dur: 25338
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1085,11 +1131,13 @@
     client_ts: 22050132357
     client_dur: 59459
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22050156562
     server_dur: 20679
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1122,11 +1170,13 @@
     client_ts: 22075288214
     client_dur: 58461
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22075311405
     server_dur: 20809
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1159,11 +1209,13 @@
     client_ts: 22100443015
     client_dur: 66715
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22100466870
     server_dur: 19761
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1196,11 +1248,13 @@
     client_ts: 22125600618
     client_dur: 65545
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22125623322
     server_dur: 20702
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1233,11 +1287,13 @@
     client_ts: 22150759363
     client_dur: 56369
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22150779993
     server_dur: 22255
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1270,11 +1326,13 @@
     client_ts: 22175906178
     client_dur: 63847
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22175929226
     server_dur: 25370
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1307,11 +1365,13 @@
     client_ts: 22201561660
     client_dur: 209987
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22201735902
     server_dur: 20781
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1344,11 +1404,13 @@
     client_ts: 22227603566
     client_dur: 61556
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22227627247
     server_dur: 19354
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1381,11 +1443,13 @@
     client_ts: 22252767831
     client_dur: 65418
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22252793033
     server_dur: 24322
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1418,11 +1482,13 @@
     client_ts: 22277925922
     client_dur: 297059
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22278187629
     server_dur: 20875
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1455,11 +1521,13 @@
     client_ts: 22303375651
     client_dur: 55580
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22303396802
     server_dur: 20656
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1492,11 +1560,13 @@
     client_ts: 22328804156
     client_dur: 83145
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22328852158
     server_dur: 21212
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1529,11 +1599,13 @@
     client_ts: 22354003523
     client_dur: 56819
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22354025291
     server_dur: 20568
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1566,11 +1638,13 @@
     client_ts: 22379156345
     client_dur: 66779
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22379180900
     server_dur: 25632
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1603,11 +1677,13 @@
     client_ts: 22404448968
     client_dur: 67438
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22404473541
     server_dur: 24816
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1640,11 +1716,13 @@
     client_ts: 22429616334
     client_dur: 74108
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22429639907
     server_dur: 32292
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1677,11 +1755,13 @@
     client_ts: 22455238568
     client_dur: 55788
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22455259634
     server_dur: 20555
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1714,11 +1794,13 @@
     client_ts: 22480383875
     client_dur: 56554
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22480404678
     server_dur: 20572
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1751,11 +1833,13 @@
     client_ts: 22505531489
     client_dur: 59218
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22505553139
     server_dur: 22859
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1788,11 +1872,13 @@
     client_ts: 22531090402
     client_dur: 56749
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22531111630
     server_dur: 20210
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1825,11 +1911,13 @@
     client_ts: 22556242635
     client_dur: 57252
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22556262293
     server_dur: 20917
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1862,11 +1950,13 @@
     client_ts: 22581426858
     client_dur: 266380
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22581641694
     server_dur: 31182
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1905,11 +1995,13 @@
     client_ts: 22606961662
     client_dur: 557886
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22606985795
     server_dur: 21756
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1942,11 +2034,13 @@
     client_ts: 22632616662
     client_dur: 57814
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22632638644
     server_dur: 20728
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -1979,11 +2073,13 @@
     client_ts: 22657890096
     client_dur: 63825
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22657912861
     server_dur: 22058
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2016,11 +2112,13 @@
     client_ts: 22683093531
     client_dur: 80792
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22683116244
     server_dur: 21278
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2059,11 +2157,13 @@
     client_ts: 22708307977
     client_dur: 67445
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22708332522
     server_dur: 26370
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2096,11 +2196,13 @@
     client_ts: 22733506276
     client_dur: 119221
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22733593661
     server_dur: 19257
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2133,11 +2235,13 @@
     client_ts: 22758727072
     client_dur: 446508
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22759138073
     server_dur: 20676
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2170,11 +2274,13 @@
     client_ts: 22784311059
     client_dur: 161051
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22784409798
     server_dur: 20874
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2207,11 +2313,13 @@
     client_ts: 22809748047
     client_dur: 57852
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22809770020
     server_dur: 20538
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2244,11 +2352,13 @@
     client_ts: 22834902012
     client_dur: 93361
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22834959559
     server_dur: 21369
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2281,11 +2391,13 @@
     client_ts: 22860898758
     client_dur: 82717
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22860942436
     server_dur: 21910
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2318,11 +2430,13 @@
     client_ts: 22886110503
     client_dur: 99316
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22886176653
     server_dur: 20849
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2355,11 +2469,13 @@
     client_ts: 22911318220
     client_dur: 66992
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22911345648
     server_dur: 22362
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2392,11 +2508,13 @@
     client_ts: 22936549304
     client_dur: 58969
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22936570719
     server_dur: 21297
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2429,11 +2547,13 @@
     client_ts: 22961701152
     client_dur: 676548
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22962339912
     server_dur: 22166
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2466,11 +2586,13 @@
     client_ts: 22987512708
     client_dur: 79649
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22987551432
     server_dur: 24288
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2509,11 +2631,13 @@
     client_ts: 23013143578
     client_dur: 61635
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23013168634
     server_dur: 20531
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2546,11 +2670,13 @@
     client_ts: 23038642421
     client_dur: 137175
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23038736570
     server_dur: 20651
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2583,11 +2709,13 @@
     client_ts: 23063886880
     client_dur: 55663
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23063908358
     server_dur: 16544
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2620,11 +2748,13 @@
     client_ts: 23089198686
     client_dur: 68926
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23089221371
     server_dur: 23561
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2663,11 +2793,13 @@
     client_ts: 23114443451
     client_dur: 64468
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23114469373
     server_dur: 23341
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2700,11 +2832,13 @@
     client_ts: 23139601722
     client_dur: 78228
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23139640251
     server_dur: 21320
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2737,11 +2871,13 @@
     client_ts: 23164771069
     client_dur: 91260
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23164821900
     server_dur: 21423
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2774,11 +2910,13 @@
     client_ts: 23189996807
     client_dur: 214050
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23190162200
     server_dur: 20825
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2817,11 +2955,13 @@
     client_ts: 23215317200
     client_dur: 57982
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23215338281
     server_dur: 21069
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2854,11 +2994,13 @@
     client_ts: 23240472994
     client_dur: 61104
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23240494382
     server_dur: 22737
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2891,11 +3033,13 @@
     client_ts: 23265633084
     client_dur: 257686
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23265855074
     server_dur: 21119
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2928,11 +3072,13 @@
     client_ts: 23294001031
     client_dur: 68360
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23294024310
     server_dur: 25000
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -2965,11 +3111,13 @@
     client_ts: 23319166709
     client_dur: 255922
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23319381423
     server_dur: 24946
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3002,11 +3150,13 @@
     client_ts: 23344525034
     client_dur: 66275
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23344548135
     server_dur: 25737
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3039,11 +3189,13 @@
     client_ts: 23369688943
     client_dur: 61329
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23369711803
     server_dur: 22061
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3076,11 +3228,13 @@
     client_ts: 23394850079
     client_dur: 540888
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23395351729
     server_dur: 22192
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3113,11 +3267,13 @@
     client_ts: 23420492464
     client_dur: 65743
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23420518127
     server_dur: 22480
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3150,11 +3306,13 @@
     client_ts: 23451713078
     client_dur: 68421
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23451738606
     server_dur: 24478
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3187,11 +3345,13 @@
     client_ts: 23476881893
     client_dur: 64782
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23476905553
     server_dur: 24602
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3224,11 +3384,13 @@
     client_ts: 23502042821
     client_dur: 820095
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23502825079
     server_dur: 23414
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3261,11 +3423,13 @@
     client_ts: 23527974198
     client_dur: 845116
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23528782089
     server_dur: 23126
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3298,11 +3462,13 @@
     client_ts: 23553917567
     client_dur: 59738
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23553940355
     server_dur: 21362
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3335,11 +3501,13 @@
     client_ts: 23579071838
     client_dur: 411519
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23579443790
     server_dur: 25365
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3372,11 +3540,13 @@
     client_ts: 23604582593
     client_dur: 210023
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23604751577
     server_dur: 24165
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3409,11 +3579,13 @@
     client_ts: 23629892322
     client_dur: 194538
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23630049856
     server_dur: 21631
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3446,11 +3618,13 @@
     client_ts: 23655179880
     client_dur: 142025
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23655273690
     server_dur: 30591
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3483,11 +3657,13 @@
     client_ts: 23680421874
     client_dur: 72852
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23680450743
     server_dur: 26736
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3520,11 +3696,13 @@
     client_ts: 23705597440
     client_dur: 470856
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23706033599
     server_dur: 21348
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3557,11 +3735,13 @@
     client_ts: 23731172177
     client_dur: 184006
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23731318438
     server_dur: 21088
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3594,11 +3774,13 @@
     client_ts: 23757576836
     client_dur: 69175
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23757598859
     server_dur: 22748
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3631,11 +3813,13 @@
     client_ts: 23782737996
     client_dur: 59451
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23782758417
     server_dur: 25123
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3668,11 +3852,13 @@
     client_ts: 23807891211
     client_dur: 62509
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23807914516
     server_dur: 23582
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3705,11 +3891,13 @@
     client_ts: 23833055418
     client_dur: 163286
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23833182220
     server_dur: 22315
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3742,11 +3930,13 @@
     client_ts: 23858313038
     client_dur: 57714
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23858335729
     server_dur: 20454
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3779,11 +3969,13 @@
     client_ts: 23883462809
     client_dur: 59212
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23883487934
     server_dur: 19666
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3816,11 +4008,13 @@
     client_ts: 23908617410
     client_dur: 67454
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23908643220
     server_dur: 25095
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3853,11 +4047,13 @@
     client_ts: 23933826058
     client_dur: 54993
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23933846583
     server_dur: 19834
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3890,11 +4086,13 @@
     client_ts: 23958974344
     client_dur: 56757
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23958997349
     server_dur: 19668
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3927,11 +4125,13 @@
     client_ts: 23984212731
     client_dur: 65425
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23984244337
     server_dur: 18760
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -3964,11 +4164,13 @@
     client_ts: 24009326493
     client_dur: 55937
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24009348026
     server_dur: 20878
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4001,11 +4203,13 @@
     client_ts: 24034474905
     client_dur: 323973
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24034762131
     server_dur: 21724
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4038,11 +4242,13 @@
     client_ts: 24059888481
     client_dur: 269355
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24060121507
     server_dur: 21099
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4075,11 +4281,13 @@
     client_ts: 24085507070
     client_dur: 65864
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24085532155
     server_dur: 24607
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4112,11 +4320,13 @@
     client_ts: 24110668836
     client_dur: 62135
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24110692176
     server_dur: 23546
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4149,11 +4359,13 @@
     client_ts: 24135822325
     client_dur: 55790
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24135843301
     server_dur: 20745
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4186,11 +4398,13 @@
     client_ts: 24160973601
     client_dur: 64296
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24160998105
     server_dur: 24373
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4223,11 +4437,13 @@
     client_ts: 24186129350
     client_dur: 61214
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24186153807
     server_dur: 21100
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4260,11 +4476,13 @@
     client_ts: 24211335205
     client_dur: 99239
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24211379278
     server_dur: 25032
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4297,11 +4515,13 @@
     client_ts: 24236559912
     client_dur: 85757
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24236597491
     server_dur: 27319
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4334,11 +4554,13 @@
     client_ts: 24261743662
     client_dur: 72120
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24261771646
     server_dur: 25209
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4371,11 +4593,13 @@
     client_ts: 24286913419
     client_dur: 330217
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24287198196
     server_dur: 22848
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4408,11 +4632,13 @@
     client_ts: 24312358034
     client_dur: 69020
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24312388724
     server_dur: 21476
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4451,11 +4677,13 @@
     client_ts: 24337574519
     client_dur: 104255
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24337613079
     server_dur: 33967
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4488,11 +4716,13 @@
     client_ts: 24362804629
     client_dur: 58822
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24362826557
     server_dur: 20783
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4525,11 +4755,13 @@
     client_ts: 24387968494
     client_dur: 63171
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24387991414
     server_dur: 24460
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4562,11 +4794,13 @@
     client_ts: 24414399633
     client_dur: 3357310
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24414422286
     server_dur: 20459
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4599,11 +4833,13 @@
     client_ts: 24443111092
     client_dur: 63218
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24443132289
     server_dur: 23717
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4636,11 +4872,13 @@
     client_ts: 24468268772
     client_dur: 63940
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24468295057
     server_dur: 21634
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4673,11 +4911,13 @@
     client_ts: 24493507908
     client_dur: 75072
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24493536500
     server_dur: 22590
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4710,11 +4950,13 @@
     client_ts: 24518715570
     client_dur: 83147
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24518746589
     server_dur: 20133
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4747,11 +4989,13 @@
     client_ts: 24544028919
     client_dur: 105487
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24544069334
     server_dur: 24026
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4784,11 +5028,13 @@
     client_ts: 24569328112
     client_dur: 106610
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24569369501
     server_dur: 41826
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4821,11 +5067,13 @@
     client_ts: 24594543032
     client_dur: 45871
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24594558488
     server_dur: 20382
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4852,11 +5100,13 @@
     client_ts: 24619690623
     client_dur: 58336
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24619710131
     server_dur: 25779
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4883,11 +5133,13 @@
     client_ts: 24644930551
     client_dur: 105892
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24644975052
     server_dur: 34388
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4920,11 +5172,13 @@
     client_ts: 24670207638
     client_dur: 54225
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24670224041
     server_dur: 26208
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4951,11 +5205,13 @@
     client_ts: 24695363832
     client_dur: 47347
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24695380382
     server_dur: 19885
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -4982,11 +5238,13 @@
     client_ts: 24720504163
     client_dur: 41635
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24720517121
     server_dur: 18951
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5013,11 +5271,13 @@
     client_ts: 24745651155
     client_dur: 50201
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24745667169
     server_dur: 21953
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5044,11 +5304,13 @@
     client_ts: 24770795510
     client_dur: 45248
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24770810644
     server_dur: 19730
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5075,11 +5337,13 @@
     client_ts: 24795949562
     client_dur: 44458
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24795964339
     server_dur: 19474
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5106,11 +5370,13 @@
     client_ts: 24821098319
     client_dur: 45736
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24821113887
     server_dur: 19916
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5137,11 +5403,13 @@
     client_ts: 24846533416
     client_dur: 52565
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24846550147
     server_dur: 20978
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5168,11 +5436,13 @@
     client_ts: 24871959612
     client_dur: 33762
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24871969021
     server_dur: 15109
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5199,11 +5469,13 @@
     client_ts: 24897089502
     client_dur: 36235
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24897101194
     server_dur: 15306
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5230,11 +5502,13 @@
     client_ts: 24922327702
     client_dur: 47487
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24922343062
     server_dur: 20895
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5261,11 +5535,13 @@
     client_ts: 24947478390
     client_dur: 50011
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24947495490
     server_dur: 21244
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5292,11 +5568,13 @@
     client_ts: 24972625739
     client_dur: 46864
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24972642054
     server_dur: 20460
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5323,11 +5601,13 @@
     client_ts: 24997766928
     client_dur: 105830
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24997800832
     server_dur: 45031
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5360,11 +5640,13 @@
     client_ts: 25023405381
     client_dur: 86423
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25023438732
     server_dur: 29757
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5397,11 +5679,13 @@
     client_ts: 25048609139
     client_dur: 56222
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25048631235
     server_dur: 16655
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5434,11 +5718,13 @@
     client_ts: 25073875780
     client_dur: 84120
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25073898205
     server_dur: 15854
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5471,11 +5757,13 @@
     client_ts: 25099064916
     client_dur: 204188
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25099198462
     server_dur: 49025
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5508,11 +5796,13 @@
     client_ts: 25124642124
     client_dur: 68889
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25124666670
     server_dur: 29243
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5539,11 +5829,13 @@
     client_ts: 25149891522
     client_dur: 60500
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25149912505
     server_dur: 27000
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5570,11 +5862,13 @@
     client_ts: 25175121734
     client_dur: 65293
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25175141892
     server_dur: 23570
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5607,11 +5901,13 @@
     client_ts: 25200283959
     client_dur: 44045
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25200298152
     server_dur: 19339
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5638,11 +5934,13 @@
     client_ts: 25225419452
     client_dur: 43144
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25225434070
     server_dur: 18615
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5669,11 +5967,13 @@
     client_ts: 25250561510
     client_dur: 82814
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25250589325
     server_dur: 36944
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5706,11 +6006,13 @@
     client_ts: 25275837002
     client_dur: 111602
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25275893327
     server_dur: 33040
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5743,11 +6045,13 @@
     client_ts: 25301779154
     client_dur: 81058
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25301805031
     server_dur: 24119
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5780,11 +6084,13 @@
     client_ts: 25327028096
     client_dur: 144639
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25327077936
     server_dur: 53753
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5817,11 +6123,13 @@
     client_ts: 25352301178
     client_dur: 837973
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25353047151
     server_dur: 48734
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5854,11 +6162,13 @@
     client_ts: 25378250275
     client_dur: 77754
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25378276198
     server_dur: 28616
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5891,11 +6201,13 @@
     client_ts: 25403423456
     client_dur: 65101
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25403445450
     server_dur: 23191
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -5929,11 +6241,13 @@
     client_ts: 25403532822
     client_dur: 243238
     client_tid: 492
+    client_pid: 492
     server_process: "system_server"
     server_thread: "binder:641_4"
     server_ts: 25403547799
     server_dur: 210707
     server_tid: 1596
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -5972,11 +6286,13 @@
     client_ts: 25403800150
     client_dur: 77678
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25403821750
     server_dur: 43829
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6009,11 +6325,13 @@
     client_ts: 25429003446
     client_dur: 55342
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25429021995
     server_dur: 20582
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6046,11 +6364,13 @@
     client_ts: 25454157095
     client_dur: 68221
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25454182918
     server_dur: 20352
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6083,11 +6403,13 @@
     client_ts: 25479325945
     client_dur: 157863
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25479422370
     server_dur: 35765
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6120,11 +6442,13 @@
     client_ts: 25504631134
     client_dur: 1082750
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25505253998
     server_dur: 109396
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6158,11 +6482,13 @@
     client_ts: 25505818197
     client_dur: 3125407
     client_tid: 492
+    client_pid: 492
     server_process: "system_server"
     server_thread: "binder:641_4"
     server_ts: 25505891588
     server_dur: 3000749
     server_tid: 1596
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -6220,11 +6546,13 @@
     client_ts: 25508998675
     client_dur: 379026
     client_tid: 492
+    client_pid: 492
     server_process: "system_server"
     server_thread: "binder:641_4"
     server_ts: 25509052778
     server_dur: 272193
     server_tid: 1596
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6257,11 +6585,13 @@
     client_ts: 25512878756
     client_dur: 151351
     client_tid: 492
+    client_pid: 492
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25512939518
     server_dur: 62943
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6294,11 +6624,13 @@
     client_ts: 21612276580
     client_dur: 39977
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21612292301
     server_dur: 14064
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6331,11 +6663,13 @@
     client_ts: 21637405403
     client_dur: 60035
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21637427285
     server_dur: 24550
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6368,11 +6702,13 @@
     client_ts: 21662560974
     client_dur: 372941
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21662897046
     server_dur: 22907
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6405,11 +6741,13 @@
     client_ts: 21688024281
     client_dur: 59297
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21688047484
     server_dur: 21173
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6442,11 +6780,13 @@
     client_ts: 21713127573
     client_dur: 63596
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21713152373
     server_dur: 21155
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6479,11 +6819,13 @@
     client_ts: 21738283156
     client_dur: 60407
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21738305184
     server_dur: 24886
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6516,11 +6858,13 @@
     client_ts: 21763445363
     client_dur: 70801
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21763467940
     server_dur: 30748
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6553,11 +6897,13 @@
     client_ts: 21788612931
     client_dur: 57076
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21788635008
     server_dur: 21359
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6590,11 +6936,13 @@
     client_ts: 21813762065
     client_dur: 61236
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21813783295
     server_dur: 25042
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6627,11 +6975,13 @@
     client_ts: 21838919344
     client_dur: 59886
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21838943470
     server_dur: 20686
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6664,11 +7014,13 @@
     client_ts: 21864144722
     client_dur: 71604
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21864171886
     server_dur: 25966
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6701,11 +7053,13 @@
     client_ts: 21889317261
     client_dur: 517889
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21889524828
     server_dur: 21638
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6738,11 +7092,13 @@
     client_ts: 21914927560
     client_dur: 59832
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21914949348
     server_dur: 23104
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6775,11 +7131,13 @@
     client_ts: 21940080190
     client_dur: 63873
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21940103851
     server_dur: 24784
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6812,11 +7170,13 @@
     client_ts: 21965236304
     client_dur: 87480
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21965262841
     server_dur: 47604
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -6855,11 +7215,13 @@
     client_ts: 21990454958
     client_dur: 89222
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21990491103
     server_dur: 25934
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6892,11 +7254,13 @@
     client_ts: 22015656850
     client_dur: 62246
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22015682089
     server_dur: 21161
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6929,11 +7293,13 @@
     client_ts: 22040814163
     client_dur: 64221
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22040838835
     server_dur: 24377
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -6966,11 +7332,13 @@
     client_ts: 22066737714
     client_dur: 128820
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22066831151
     server_dur: 19563
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7003,11 +7371,13 @@
     client_ts: 22091960087
     client_dur: 58325
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22091982875
     server_dur: 21344
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7040,11 +7410,13 @@
     client_ts: 22117113923
     client_dur: 63410
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22117137718
     server_dur: 23868
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7077,11 +7449,13 @@
     client_ts: 22142267357
     client_dur: 59986
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22142291376
     server_dur: 21134
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7114,11 +7488,13 @@
     client_ts: 22167423522
     client_dur: 59967
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22167445520
     server_dur: 22511
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7151,11 +7527,13 @@
     client_ts: 22192579344
     client_dur: 60820
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22192603786
     server_dur: 21006
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7188,11 +7566,13 @@
     client_ts: 22217730341
     client_dur: 55283
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22217750903
     server_dur: 20680
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7225,11 +7605,13 @@
     client_ts: 22242880300
     client_dur: 71100
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22242905309
     server_dur: 26682
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7262,11 +7644,13 @@
     client_ts: 22268043393
     client_dur: 409558
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22268414904
     server_dur: 21405
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7299,11 +7683,13 @@
     client_ts: 22293548146
     client_dur: 63871
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22293572042
     server_dur: 22980
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7336,11 +7722,13 @@
     client_ts: 22318716098
     client_dur: 339826
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22318736854
     server_dur: 23010
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7373,11 +7761,13 @@
     client_ts: 22344146679
     client_dur: 110188
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22344218394
     server_dur: 24011
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7410,11 +7800,13 @@
     client_ts: 22369334226
     client_dur: 94388
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22369355609
     server_dur: 48856
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7447,11 +7839,13 @@
     client_ts: 22394536230
     client_dur: 62982
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22394561551
     server_dur: 22059
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7484,11 +7878,13 @@
     client_ts: 22419697576
     client_dur: 61323
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22419722756
     server_dur: 21994
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7521,11 +7917,13 @@
     client_ts: 22445031509
     client_dur: 50515
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22445050820
     server_dur: 17128
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7558,11 +7956,13 @@
     client_ts: 22470174653
     client_dur: 55822
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22470196239
     server_dur: 21286
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7595,11 +7995,13 @@
     client_ts: 22495325972
     client_dur: 68180
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22495350975
     server_dur: 26711
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7632,11 +8034,13 @@
     client_ts: 22521360985
     client_dur: 234231
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22521382730
     server_dur: 21186
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7669,11 +8073,13 @@
     client_ts: 22546697891
     client_dur: 98444
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22546761058
     server_dur: 19881
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7706,11 +8112,13 @@
     client_ts: 22571892907
     client_dur: 66101
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22571916881
     server_dur: 25795
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7743,11 +8151,13 @@
     client_ts: 22597062070
     client_dur: 259462
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22597272531
     server_dur: 34173
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7780,11 +8190,13 @@
     client_ts: 22622418577
     client_dur: 59816
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22622443274
     server_dur: 21430
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7817,11 +8229,13 @@
     client_ts: 22650180912
     client_dur: 473997
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22650255041
     server_dur: 19254
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7854,11 +8268,13 @@
     client_ts: 22675764005
     client_dur: 70615
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22675789713
     server_dur: 25069
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7891,11 +8307,13 @@
     client_ts: 22701432879
     client_dur: 450491
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22701762194
     server_dur: 23978
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7928,11 +8346,13 @@
     client_ts: 22726978606
     client_dur: 401005
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22727339544
     server_dur: 23475
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -7965,11 +8385,13 @@
     client_ts: 22752478293
     client_dur: 586574
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22753025360
     server_dur: 21422
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8002,11 +8424,13 @@
     client_ts: 22778161650
     client_dur: 349397
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22778472830
     server_dur: 22980
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8039,11 +8463,13 @@
     client_ts: 22803612048
     client_dur: 67634
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22803636072
     server_dur: 23917
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8076,11 +8502,13 @@
     client_ts: 22828777874
     client_dur: 318230
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22828822556
     server_dur: 26877
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8113,11 +8541,13 @@
     client_ts: 22854196228
     client_dur: 561632
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22854720488
     server_dur: 21460
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8150,11 +8580,13 @@
     client_ts: 22879856680
     client_dur: 63364
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22879882339
     server_dur: 22238
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8187,11 +8619,13 @@
     client_ts: 22905018627
     client_dur: 66122
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22905041577
     server_dur: 26326
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8224,11 +8658,13 @@
     client_ts: 22930183511
     client_dur: 67161
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22930213119
     server_dur: 23605
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8261,11 +8697,13 @@
     client_ts: 22955349414
     client_dur: 156341
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22955463649
     server_dur: 24599
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8298,11 +8736,13 @@
     client_ts: 22980607439
     client_dur: 65794
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22980629549
     server_dur: 26192
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8335,11 +8775,13 @@
     client_ts: 23005774838
     client_dur: 62936
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23005798704
     server_dur: 23801
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8372,11 +8814,13 @@
     client_ts: 23030941978
     client_dur: 89285
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23030973182
     server_dur: 37855
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8409,11 +8853,13 @@
     client_ts: 23056174448
     client_dur: 200618
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23056329883
     server_dur: 23980
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8446,11 +8892,13 @@
     client_ts: 23081467052
     client_dur: 59125
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23081486828
     server_dur: 24680
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8483,11 +8931,13 @@
     client_ts: 23106620846
     client_dur: 61616
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23106642188
     server_dur: 24012
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8520,11 +8970,13 @@
     client_ts: 23131777328
     client_dur: 123447
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23131861232
     server_dur: 22637
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8557,11 +9009,13 @@
     client_ts: 23157002672
     client_dur: 79156
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23157030248
     server_dur: 27226
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8594,11 +9048,13 @@
     client_ts: 23182173903
     client_dur: 376646
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23182500065
     server_dur: 21132
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8631,11 +9087,13 @@
     client_ts: 23207650439
     client_dur: 67278
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23207680535
     server_dur: 23155
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8668,11 +9126,13 @@
     client_ts: 23233851428
     client_dur: 892848
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23233871293
     server_dur: 23312
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8705,11 +9165,13 @@
     client_ts: 23259841421
     client_dur: 63174
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23259863994
     server_dur: 23889
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8742,11 +9204,13 @@
     client_ts: 23284999517
     client_dur: 56437
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23285019554
     server_dur: 21307
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8779,11 +9243,13 @@
     client_ts: 23310151865
     client_dur: 642229
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23310751409
     server_dur: 26103
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8816,11 +9282,13 @@
     client_ts: 23335905400
     client_dur: 357982
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23336222503
     server_dur: 25821
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8853,11 +9321,13 @@
     client_ts: 23361328882
     client_dur: 63784
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23361351852
     server_dur: 24700
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8890,11 +9360,13 @@
     client_ts: 23386869959
     client_dur: 66376
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23386894766
     server_dur: 25613
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8927,11 +9399,13 @@
     client_ts: 23412468379
     client_dur: 144242
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23412492591
     server_dur: 28309
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -8964,11 +9438,13 @@
     client_ts: 23437712307
     client_dur: 61646
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23437733692
     server_dur: 21877
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9001,11 +9477,13 @@
     client_ts: 23462870779
     client_dur: 64694
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23462898239
     server_dur: 21829
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9038,11 +9516,13 @@
     client_ts: 23488042638
     client_dur: 69970
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23488068942
     server_dur: 26303
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9075,11 +9555,13 @@
     client_ts: 23513211192
     client_dur: 57998
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23513232838
     server_dur: 22355
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9112,11 +9594,13 @@
     client_ts: 23538364057
     client_dur: 90805
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23538417034
     server_dur: 21543
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9149,11 +9633,13 @@
     client_ts: 23563555747
     client_dur: 62301
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23563577896
     server_dur: 23477
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9186,11 +9672,13 @@
     client_ts: 23588716088
     client_dur: 122069
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23588796199
     server_dur: 21803
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9223,11 +9711,13 @@
     client_ts: 23613942195
     client_dur: 406560
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23614311041
     server_dur: 21665
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9260,11 +9750,13 @@
     client_ts: 23639449764
     client_dur: 297834
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23639707345
     server_dur: 24585
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9297,11 +9789,13 @@
     client_ts: 23664840926
     client_dur: 64026
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23664863549
     server_dur: 25828
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9334,11 +9828,13 @@
     client_ts: 23689999571
     client_dur: 60667
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23690020989
     server_dur: 24039
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9371,11 +9867,13 @@
     client_ts: 23715668567
     client_dur: 55651
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23715687865
     server_dur: 20534
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9408,11 +9906,13 @@
     client_ts: 23740820398
     client_dur: 69779
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23740846289
     server_dur: 26005
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9445,11 +9945,13 @@
     client_ts: 23765983216
     client_dur: 63111
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23766008333
     server_dur: 22489
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9482,11 +9984,13 @@
     client_ts: 23791142714
     client_dur: 62184
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23791165515
     server_dur: 24176
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9519,11 +10023,13 @@
     client_ts: 23816748979
     client_dur: 97527
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23816810994
     server_dur: 19216
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9556,11 +10062,13 @@
     client_ts: 23841937008
     client_dur: 265351
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23842045870
     server_dur: 21030
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9593,11 +10101,13 @@
     client_ts: 23867298197
     client_dur: 65269
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23867323342
     server_dur: 24762
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9630,11 +10140,13 @@
     client_ts: 23892458661
     client_dur: 61895
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23892481485
     server_dur: 23511
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9667,11 +10179,13 @@
     client_ts: 23917612205
     client_dur: 60756
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23917632161
     server_dur: 27790
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9704,11 +10218,13 @@
     client_ts: 23942767445
     client_dur: 56639
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23942789248
     server_dur: 20150
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9741,11 +10257,13 @@
     client_ts: 23967917345
     client_dur: 61714
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23967941212
     server_dur: 23677
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9778,11 +10296,13 @@
     client_ts: 23993073217
     client_dur: 61426
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23993098746
     server_dur: 21011
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9815,11 +10335,13 @@
     client_ts: 24018227157
     client_dur: 154788
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24018346321
     server_dur: 21354
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9852,11 +10374,13 @@
     client_ts: 24043472861
     client_dur: 56072
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24043492721
     server_dur: 20952
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9889,11 +10413,13 @@
     client_ts: 24068623147
     client_dur: 61422
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24068647003
     server_dur: 23307
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9926,11 +10452,13 @@
     client_ts: 24094174551
     client_dur: 60440
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24094198265
     server_dur: 20963
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -9963,11 +10491,13 @@
     client_ts: 24119330336
     client_dur: 62796
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24119353765
     server_dur: 24014
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10000,11 +10530,13 @@
     client_ts: 24144486541
     client_dur: 57521
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24144507336
     server_dur: 20651
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10037,11 +10569,13 @@
     client_ts: 24169644272
     client_dur: 59318
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24169667507
     server_dur: 21848
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10074,11 +10608,13 @@
     client_ts: 24194796993
     client_dur: 61133
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24194818919
     server_dur: 24981
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10111,11 +10647,13 @@
     client_ts: 24219976971
     client_dur: 80874
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24220009018
     server_dur: 24029
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10148,11 +10686,13 @@
     client_ts: 24245182141
     client_dur: 75715
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24245212188
     server_dur: 24869
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10185,11 +10725,13 @@
     client_ts: 24270354623
     client_dur: 72978
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24270381508
     server_dur: 24460
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10222,11 +10764,13 @@
     client_ts: 24296497808
     client_dur: 74119
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24296524147
     server_dur: 25619
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10259,11 +10803,13 @@
     client_ts: 24321677437
     client_dur: 1068351
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24322701227
     server_dur: 22458
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10296,11 +10842,13 @@
     client_ts: 24349464998
     client_dur: 600120
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24349498235
     server_dur: 21721
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10333,11 +10881,13 @@
     client_ts: 24375203138
     client_dur: 200814
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24375371665
     server_dur: 20774
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10370,11 +10920,13 @@
     client_ts: 24400499298
     client_dur: 1655381
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24400525814
     server_dur: 20740
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10407,11 +10959,13 @@
     client_ts: 24427255856
     client_dur: 74073
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24427281368
     server_dur: 33400
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10444,11 +10998,13 @@
     client_ts: 24452425092
     client_dur: 66019
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24452453488
     server_dur: 22713
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10481,11 +11037,13 @@
     client_ts: 24477584081
     client_dur: 77570
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24477603311
     server_dur: 20359
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10518,11 +11076,13 @@
     client_ts: 24502766882
     client_dur: 85030
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24502792394
     server_dur: 34540
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10555,11 +11115,13 @@
     client_ts: 24528063792
     client_dur: 88744
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24528100762
     server_dur: 19035
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10592,11 +11154,13 @@
     client_ts: 24553269933
     client_dur: 102641
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24553320732
     server_dur: 19753
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10629,11 +11193,13 @@
     client_ts: 24578496582
     client_dur: 75733
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24578522139
     server_dur: 31879
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10666,11 +11232,13 @@
     client_ts: 24603666160
     client_dur: 32902
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24603676548
     server_dur: 13734
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10703,11 +11271,13 @@
     client_ts: 24628827055
     client_dur: 77433
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24628855199
     server_dur: 31376
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10740,11 +11310,13 @@
     client_ts: 24654016554
     client_dur: 82862
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24654047419
     server_dur: 27392
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10777,11 +11349,13 @@
     client_ts: 24679300128
     client_dur: 51067
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24679317330
     server_dur: 23251
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10814,11 +11388,13 @@
     client_ts: 24704445330
     client_dur: 53313
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24704463794
     server_dur: 20543
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10851,11 +11427,13 @@
     client_ts: 24729604221
     client_dur: 73960
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24729643378
     server_dur: 19819
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10888,11 +11466,13 @@
     client_ts: 24754787089
     client_dur: 79191
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24754812563
     server_dur: 21216
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10925,11 +11505,13 @@
     client_ts: 24779973546
     client_dur: 61655
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24779997866
     server_dur: 21840
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10962,11 +11544,13 @@
     client_ts: 24805151631
     client_dur: 65179
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24805176625
     server_dur: 24502
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -10999,11 +11583,13 @@
     client_ts: 24830317956
     client_dur: 66289
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24830344368
     server_dur: 21990
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11036,11 +11622,13 @@
     client_ts: 24855481377
     client_dur: 65228
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24855502595
     server_dur: 27360
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11073,11 +11661,13 @@
     client_ts: 24880648226
     client_dur: 55511
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24880667129
     server_dur: 21590
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11110,11 +11700,13 @@
     client_ts: 24909335836
     client_dur: 55761
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24909354477
     server_dur: 24104
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11147,11 +11739,13 @@
     client_ts: 24934490094
     client_dur: 58244
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24934509872
     server_dur: 22084
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11184,11 +11778,13 @@
     client_ts: 24959652634
     client_dur: 81523
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24959684793
     server_dur: 28526
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11221,11 +11817,13 @@
     client_ts: 24984851591
     client_dur: 68470
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24984877747
     server_dur: 26966
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11258,11 +11856,13 @@
     client_ts: 25010040890
     client_dur: 90947
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25010071568
     server_dur: 37280
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11295,11 +11895,13 @@
     client_ts: 25035275877
     client_dur: 59761
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25035296221
     server_dur: 25210
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11332,11 +11934,13 @@
     client_ts: 25060426543
     client_dur: 81204
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25060443769
     server_dur: 22941
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11369,11 +11973,13 @@
     client_ts: 25085672790
     client_dur: 95011
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25085716849
     server_dur: 37413
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11406,11 +12012,13 @@
     client_ts: 25110861456
     client_dur: 53363
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25110879022
     server_dur: 22707
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11443,11 +12051,13 @@
     client_ts: 25136012642
     client_dur: 51978
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25136030960
     server_dur: 22166
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11480,11 +12090,13 @@
     client_ts: 25161156360
     client_dur: 68046
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25161174689
     server_dur: 37221
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11517,11 +12129,13 @@
     client_ts: 25186325904
     client_dur: 37128
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25186338341
     server_dur: 14526
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11554,11 +12168,13 @@
     client_ts: 25211748648
     client_dur: 44699
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25211763861
     server_dur: 18676
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11591,11 +12207,13 @@
     client_ts: 25236887649
     client_dur: 51265
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25236905107
     server_dur: 21519
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11628,11 +12246,13 @@
     client_ts: 25262053873
     client_dur: 82396
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25262085792
     server_dur: 26917
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11665,11 +12285,13 @@
     client_ts: 25287387704
     client_dur: 103899
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25287423221
     server_dur: 43908
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11702,11 +12324,13 @@
     client_ts: 25312712971
     client_dur: 248879
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25312865501
     server_dur: 53943
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11739,11 +12363,13 @@
     client_ts: 25338145653
     client_dur: 118734
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25338183347
     server_dur: 50843
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11776,11 +12402,13 @@
     client_ts: 25363427959
     client_dur: 127185
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25363473345
     server_dur: 52393
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11813,11 +12441,13 @@
     client_ts: 25388666272
     client_dur: 311561
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25388926887
     server_dur: 27556
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -11850,11 +12480,13 @@
     client_ts: 25389019468
     client_dur: 2257654
     client_tid: 537
+    client_pid: 537
     server_process: "system_server"
     server_thread: "binder:641_2"
     server_ts: 25389272037
     server_dur: 1993270
     server_tid: 656
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -11912,11 +12544,13 @@
     client_ts: 25391362853
     client_dur: 2138663
     client_tid: 537
+    client_pid: 537
     server_process: "system_server"
     server_thread: "binder:641_1"
     server_ts: 25391432268
     server_dur: 2057673
     server_tid: 655
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -11973,11 +12607,13 @@
     client_ts: 25393529331
     client_dur: 72907
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25393544112
     server_dur: 48279
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12010,11 +12646,13 @@
     client_ts: 25418715954
     client_dur: 46115
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25418731360
     server_dur: 20385
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12047,11 +12685,13 @@
     client_ts: 25443850387
     client_dur: 44974
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25443866014
     server_dur: 19042
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12084,11 +12724,13 @@
     client_ts: 25469057379
     client_dur: 66877
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25469081511
     server_dur: 26836
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12121,11 +12763,13 @@
     client_ts: 25494306485
     client_dur: 127198
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25494352512
     server_dur: 49015
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12158,11 +12802,13 @@
     client_ts: 25519756306
     client_dur: 101379
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25519800487
     server_dur: 31778
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12196,11 +12842,13 @@
     client_ts: 25519893501
     client_dur: 154940
     client_tid: 537
+    client_pid: 537
     server_process: "system_server"
     server_thread: "binder:641_4"
     server_ts: 25519915012
     server_dur: 115492
     server_tid: 1596
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12234,11 +12882,13 @@
     client_ts: 25520078379
     client_dur: 176159
     client_tid: 537
+    client_pid: 537
     server_process: "system_server"
     server_thread: "binder:641_1"
     server_ts: 25520102430
     server_dur: 134309
     server_tid: 655
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12272,11 +12922,13 @@
     client_ts: 25520281012
     client_dur: 123400
     client_tid: 537
+    client_pid: 537
     server_process: "system_server"
     server_thread: "binder:641_2"
     server_ts: 25520299524
     server_dur: 88243
     server_tid: 656
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12309,11 +12961,13 @@
     client_ts: 25520423828
     client_dur: 343243
     client_tid: 537
+    client_pid: 537
     server_process: "system_server"
     server_thread: "binder:641_3"
     server_ts: 25520612948
     server_dur: 123445
     server_tid: 1595
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12346,11 +13000,13 @@
     client_ts: 25520890215
     client_dur: 293220
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25521075472
     server_dur: 82900
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12384,11 +13040,13 @@
     client_ts: 25521216286
     client_dur: 489554
     client_tid: 537
+    client_pid: 537
     server_process: "system_server"
     server_thread: "binder:641_3"
     server_ts: 25521243526
     server_dur: 435659
     server_tid: 1595
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -12427,11 +13085,13 @@
     client_ts: 25523480228
     client_dur: 2277042
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/hwservicemanager"
     server_thread: "hwservicemanage"
     server_ts: 25523653053
     server_dur: 2085804
     server_tid: 247
+    server_pid: 247
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -12476,11 +13136,13 @@
     client_ts: 25525828575
     client_dur: 674113
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/hwservicemanager"
     server_thread: "hwservicemanage"
     server_ts: 25526007038
     server_dur: 466892
     server_tid: 247
+    server_pid: 247
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -12525,11 +13187,13 @@
     client_ts: 25529470300
     client_dur: 103937
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25529493228
     server_dur: 62956
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12562,11 +13226,13 @@
     client_ts: 25529642910
     client_dur: 106592
     client_tid: 537
+    client_pid: 537
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25529669348
     server_dur: 64544
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12599,11 +13265,13 @@
     client_ts: 21610888219
     client_dur: 1404460
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21610912981
     server_dur: 24345
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12636,11 +13304,13 @@
     client_ts: 21713165109
     client_dur: 1236372
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21713185409
     server_dur: 12103
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12673,11 +13343,13 @@
     client_ts: 21817588572
     client_dur: 329198
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21817646283
     server_dur: 29874
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12710,11 +13382,13 @@
     client_ts: 21918044833
     client_dur: 54295
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 21918066018
     server_dur: 22515
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12747,11 +13421,13 @@
     client_ts: 22018230040
     client_dur: 4199277
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22018253018
     server_dur: 20817
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12784,11 +13460,13 @@
     client_ts: 22128615818
     client_dur: 66352
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22128641106
     server_dur: 21332
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12821,11 +13499,13 @@
     client_ts: 22231107021
     client_dur: 6202865
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22231126269
     server_dur: 21216
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12858,11 +13538,13 @@
     client_ts: 22338230784
     client_dur: 5927748
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22338257085
     server_dur: 22271
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12895,11 +13577,13 @@
     client_ts: 22444315008
     client_dur: 360477
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22444338001
     server_dur: 22884
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12932,11 +13616,13 @@
     client_ts: 22545342573
     client_dur: 1367091
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22545364958
     server_dur: 22936
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -12969,11 +13655,13 @@
     client_ts: 22646870376
     client_dur: 61414
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22646893001
     server_dur: 24570
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13006,11 +13694,13 @@
     client_ts: 22747766863
     client_dur: 1605566
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22749199929
     server_dur: 25123
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13043,11 +13733,13 @@
     client_ts: 22849527732
     client_dur: 3775048
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22849787260
     server_dur: 22559
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13080,11 +13772,13 @@
     client_ts: 22955387074
     client_dur: 5918097
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 22955504595
     server_dur: 12187
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13117,11 +13811,13 @@
     client_ts: 23063243768
     client_dur: 1153069
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23063268117
     server_dur: 21864
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13154,11 +13850,13 @@
     client_ts: 23171834613
     client_dur: 468230
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23171888187
     server_dur: 23659
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13191,11 +13889,13 @@
     client_ts: 23274482309
     client_dur: 56450
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23274502428
     server_dur: 22204
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13228,11 +13928,13 @@
     client_ts: 23375517889
     client_dur: 2282317
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23377319838
     server_dur: 23748
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13265,11 +13967,13 @@
     client_ts: 23479410599
     client_dur: 60331
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23479434099
     server_dur: 24056
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13302,11 +14006,13 @@
     client_ts: 23581096165
     client_dur: 380036
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23581329034
     server_dur: 23039
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13339,11 +14045,13 @@
     client_ts: 23683521915
     client_dur: 66608
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23683548740
     server_dur: 23628
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13376,11 +14084,13 @@
     client_ts: 23788017588
     client_dur: 110886
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23788091294
     server_dur: 22793
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13413,11 +14123,13 @@
     client_ts: 23892497829
     client_dur: 5298146
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23892520529
     server_dur: 12354
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13450,11 +14162,13 @@
     client_ts: 23997916363
     client_dur: 128256
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 23997934258
     server_dur: 22034
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13487,11 +14201,13 @@
     client_ts: 24100588444
     client_dur: 79761
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24100613596
     server_dur: 21941
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13524,11 +14240,13 @@
     client_ts: 24203608109
     client_dur: 1476962
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24203662175
     server_dur: 25824
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13561,11 +14279,13 @@
     client_ts: 24305487641
     client_dur: 69000
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24305515718
     server_dur: 25348
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13598,11 +14318,13 @@
     client_ts: 24405940362
     client_dur: 114844
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24405962114
     server_dur: 22212
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13635,11 +14357,13 @@
     client_ts: 24506183075
     client_dur: 76130
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24506212466
     server_dur: 21817
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13672,11 +14396,13 @@
     client_ts: 24606672569
     client_dur: 71411
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24606692709
     server_dur: 37402
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13709,11 +14435,13 @@
     client_ts: 24706847915
     client_dur: 77826
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24706871511
     server_dur: 21505
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13746,11 +14474,13 @@
     client_ts: 24807614065
     client_dur: 142762
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24807635682
     server_dur: 20956
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13783,11 +14513,13 @@
     client_ts: 24909374599
     client_dur: 3171973
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24909393940
     server_dur: 10997
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13820,11 +14552,13 @@
     client_ts: 25012679868
     client_dur: 112287
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25012708944
     server_dur: 35550
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13857,11 +14591,13 @@
     client_ts: 25112924292
     client_dur: 43230
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25112938063
     server_dur: 18263
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13894,11 +14630,13 @@
     client_ts: 25213072326
     client_dur: 33395
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25213083753
     server_dur: 13289
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13931,11 +14669,13 @@
     client_ts: 25313259127
     client_dur: 150680
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25313317394
     server_dur: 52236
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -13968,11 +14708,13 @@
     client_ts: 25415404685
     client_dur: 1470768
     client_tid: 1225
+    client_pid: 555
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25416823355
     server_dur: 22986
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14005,11 +14747,13 @@
     client_ts: 25417416478
     client_dur: 321332
     client_tid: 1225
+    client_pid: 555
     server_process: "system_server"
     server_thread: "binder:641_4"
     server_ts: 25417428728
     server_dur: 140719
     server_tid: 1596
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14042,11 +14786,13 @@
     client_ts: 25867907972
     client_dur: 68305
     client_tid: 522
+    client_pid: 496
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25867933710
     server_dur: 25394
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14079,11 +14825,13 @@
     client_ts: 21648847518
     client_dur: 138863
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21648864955
     server_dur: 110424
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14116,11 +14864,13 @@
     client_ts: 21649020222
     client_dur: 1298536
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21649025816
     server_dur: 1271373
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14153,11 +14903,13 @@
     client_ts: 21650405554
     client_dur: 21176
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21650412827
     server_dur: 7662
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14190,11 +14942,13 @@
     client_ts: 21732179696
     client_dur: 66330
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21732194327
     server_dur: 42279
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14227,11 +14981,13 @@
     client_ts: 21732276479
     client_dur: 998493
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21732281653
     server_dur: 980816
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14264,11 +15020,13 @@
     client_ts: 21747805001
     client_dur: 32253
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21747815991
     server_dur: 13234
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14301,11 +15059,13 @@
     client_ts: 21815501160
     client_dur: 67864
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21815515197
     server_dur: 44624
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14338,11 +15098,13 @@
     client_ts: 21815599518
     client_dur: 1843570
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21815604505
     server_dur: 1825932
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14375,11 +15137,13 @@
     client_ts: 21817527536
     client_dur: 20911
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21817534229
     server_dur: 6822
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14412,11 +15176,13 @@
     client_ts: 21898832978
     client_dur: 61578
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21898846685
     server_dur: 38670
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14449,11 +15215,13 @@
     client_ts: 21898923783
     client_dur: 1096630
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21898928634
     server_dur: 1080195
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14486,11 +15254,13 @@
     client_ts: 21914447745
     client_dur: 30172
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21914458873
     server_dur: 11417
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14523,11 +15293,13 @@
     client_ts: 21982278493
     client_dur: 137675
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21982310594
     server_dur: 80114
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14560,11 +15332,13 @@
     client_ts: 21982477699
     client_dur: 1235182
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21982492293
     server_dur: 1187140
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14597,11 +15371,13 @@
     client_ts: 21983894427
     client_dur: 56732
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 21983913100
     server_dur: 18080
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14634,11 +15410,13 @@
     client_ts: 22065483496
     client_dur: 63685
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22065497670
     server_dur: 40803
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14671,11 +15449,13 @@
     client_ts: 22065575735
     client_dur: 1062604
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22065580382
     server_dur: 1045862
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14708,11 +15488,13 @@
     client_ts: 22081125903
     client_dur: 31676
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22081137566
     server_dur: 12008
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14745,11 +15527,13 @@
     client_ts: 22148826292
     client_dur: 66432
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22148840763
     server_dur: 42889
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14782,11 +15566,13 @@
     client_ts: 22148922210
     client_dur: 1055199
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22148927117
     server_dur: 1037009
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14819,11 +15605,13 @@
     client_ts: 22150072810
     client_dur: 21373
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22150080025
     server_dur: 7715
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14856,11 +15644,13 @@
     client_ts: 22232166904
     client_dur: 64562
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22232181958
     server_dur: 40112
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14893,11 +15683,13 @@
     client_ts: 22232260319
     client_dur: 1084947
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22232265525
     server_dur: 1065811
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14930,11 +15722,13 @@
     client_ts: 22247787616
     client_dur: 29515
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22247797746
     server_dur: 12125
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -14967,11 +15761,13 @@
     client_ts: 22315503408
     client_dur: 75460
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22315519682
     server_dur: 49022
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15004,11 +15800,13 @@
     client_ts: 22315610938
     client_dur: 1046448
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22315616049
     server_dur: 1029776
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15041,11 +15839,13 @@
     client_ts: 22316735903
     client_dur: 21152
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22316742808
     server_dur: 7783
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15078,11 +15878,13 @@
     client_ts: 22398839076
     client_dur: 65963
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22398854027
     server_dur: 42248
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15115,11 +15917,13 @@
     client_ts: 22398960806
     client_dur: 1018998
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22398966568
     server_dur: 1000410
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15152,11 +15956,13 @@
     client_ts: 22414462930
     client_dur: 32955
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22414474372
     server_dur: 13302
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15189,11 +15995,13 @@
     client_ts: 22482179365
     client_dur: 71661
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22482194253
     server_dur: 47932
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15226,11 +16034,13 @@
     client_ts: 22482282793
     client_dur: 955159
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22482287911
     server_dur: 938324
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15263,11 +16073,13 @@
     client_ts: 22497465809
     client_dur: 39624
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22497477915
     server_dur: 17691
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15300,11 +16112,13 @@
     client_ts: 22565521231
     client_dur: 66993
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22565536155
     server_dur: 42982
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15337,11 +16151,13 @@
     client_ts: 22565618391
     client_dur: 1026658
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22565623264
     server_dur: 1009748
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15374,11 +16190,13 @@
     client_ts: 22581241130
     client_dur: 37185
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22581254325
     server_dur: 15879
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15411,11 +16229,13 @@
     client_ts: 22648855410
     client_dur: 78080
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22648872617
     server_dur: 51463
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15448,11 +16268,13 @@
     client_ts: 22648965876
     client_dur: 1080456
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22648971095
     server_dur: 1061465
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15485,11 +16307,13 @@
     client_ts: 22650134321
     client_dur: 21145
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22650140974
     server_dur: 7869
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15522,11 +16346,13 @@
     client_ts: 22732183976
     client_dur: 84922
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22732199541
     server_dur: 59268
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15559,11 +16385,13 @@
     client_ts: 22732300442
     client_dur: 1084256
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22732305487
     server_dur: 1066498
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15596,11 +16424,13 @@
     client_ts: 22749316211
     client_dur: 37389
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22749329367
     server_dur: 15449
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15633,11 +16463,13 @@
     client_ts: 22815554390
     client_dur: 80259
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22815571850
     server_dur: 52908
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15670,11 +16502,13 @@
     client_ts: 22815665677
     client_dur: 1846724
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22815670690
     server_dur: 1827939
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15707,11 +16541,13 @@
     client_ts: 22817599826
     client_dur: 21169
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22817606552
     server_dur: 8080
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15744,11 +16580,13 @@
     client_ts: 22898837991
     client_dur: 66016
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22898853740
     server_dur: 41111
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15781,11 +16619,13 @@
     client_ts: 22898932620
     client_dur: 1881472
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22898937509
     server_dur: 1862476
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15818,11 +16658,13 @@
     client_ts: 22914460532
     client_dur: 34598
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22914472324
     server_dur: 14518
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15855,11 +16697,13 @@
     client_ts: 22982182870
     client_dur: 80394
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22982200371
     server_dur: 52999
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15892,11 +16736,13 @@
     client_ts: 22982296391
     client_dur: 1052212
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22982301600
     server_dur: 1033314
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15929,11 +16775,13 @@
     client_ts: 22983445756
     client_dur: 23169
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 22983452175
     server_dur: 10357
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -15966,11 +16814,13 @@
     client_ts: 23065513683
     client_dur: 74423
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23065529019
     server_dur: 49067
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16003,11 +16853,13 @@
     client_ts: 23065619219
     client_dur: 1146958
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23065624303
     server_dur: 1129990
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16040,11 +16892,13 @@
     client_ts: 23081285291
     client_dur: 45213
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23081307603
     server_dur: 14325
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16077,11 +16931,13 @@
     client_ts: 23148835709
     client_dur: 76791
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23148852272
     server_dur: 50863
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16114,11 +16970,13 @@
     client_ts: 23148944247
     client_dur: 1986947
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23148949481
     server_dur: 1965225
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16151,11 +17009,13 @@
     client_ts: 23151027818
     client_dur: 25087
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23151036483
     server_dur: 9385
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16188,11 +17048,13 @@
     client_ts: 23232183799
     client_dur: 75511
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23232201226
     server_dur: 48102
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16225,11 +17087,13 @@
     client_ts: 23232290686
     client_dur: 1140969
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23232295646
     server_dur: 1123404
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16262,11 +17126,13 @@
     client_ts: 23249891699
     client_dur: 37797
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23249904526
     server_dur: 16397
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16299,11 +17165,13 @@
     client_ts: 23315566931
     client_dur: 110557
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23315592450
     server_dur: 69421
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16336,11 +17204,13 @@
     client_ts: 23315722066
     client_dur: 2606042
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23315729839
     server_dur: 2583671
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16373,11 +17243,13 @@
     client_ts: 23318420005
     client_dur: 21345
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23318427118
     server_dur: 7958
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16410,11 +17282,13 @@
     client_ts: 23398873863
     client_dur: 68770
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23398889934
     server_dur: 41798
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16447,11 +17321,13 @@
     client_ts: 23398973230
     client_dur: 1176570
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23398978271
     server_dur: 1158688
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16484,11 +17360,13 @@
     client_ts: 23416727246
     client_dur: 33867
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23416739503
     server_dur: 14319
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16521,11 +17399,13 @@
     client_ts: 23482185608
     client_dur: 80279
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23482202317
     server_dur: 53655
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16558,11 +17438,13 @@
     client_ts: 23482297909
     client_dur: 1040841
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23482303086
     server_dur: 1022859
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16595,11 +17477,13 @@
     client_ts: 23483420403
     client_dur: 20295
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23483427104
     server_dur: 6793
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16632,11 +17516,13 @@
     client_ts: 23566095373
     client_dur: 80168
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23566112317
     server_dur: 53405
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16669,11 +17555,13 @@
     client_ts: 23566207004
     client_dur: 1080032
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23566212869
     server_dur: 1062341
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16706,11 +17594,13 @@
     client_ts: 23581426699
     client_dur: 33178
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23581438242
     server_dur: 13572
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16743,11 +17633,13 @@
     client_ts: 23648877211
     client_dur: 74827
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23648892093
     server_dur: 50840
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16780,11 +17672,13 @@
     client_ts: 23648984124
     client_dur: 1869563
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23648989106
     server_dur: 1850394
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16817,11 +17711,13 @@
     client_ts: 23650943350
     client_dur: 22389
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23650950419
     server_dur: 8699
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16854,11 +17750,13 @@
     client_ts: 23732162997
     client_dur: 66948
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23732178223
     server_dur: 42065
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16891,11 +17789,13 @@
     client_ts: 23732260275
     client_dur: 1099825
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23732265173
     server_dur: 1083011
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16928,11 +17828,13 @@
     client_ts: 23747796852
     client_dur: 29683
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23747806637
     server_dur: 12580
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -16965,11 +17867,13 @@
     client_ts: 23815516337
     client_dur: 68846
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23815530730
     server_dur: 44894
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17002,11 +17906,13 @@
     client_ts: 23815619259
     client_dur: 986718
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23815623956
     server_dur: 969888
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17039,11 +17945,13 @@
     client_ts: 23816691292
     client_dur: 20206
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23816697719
     server_dur: 7427
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17076,11 +17984,13 @@
     client_ts: 23898849538
     client_dur: 73093
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23898865505
     server_dur: 44882
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17113,11 +18023,13 @@
     client_ts: 23898957000
     client_dur: 1065096
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23898962241
     server_dur: 1045751
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17150,11 +18062,13 @@
     client_ts: 23914455173
     client_dur: 29880
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23914466130
     server_dur: 11108
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17187,11 +18101,13 @@
     client_ts: 23982180549
     client_dur: 85583
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23982199040
     server_dur: 52914
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17224,11 +18140,13 @@
     client_ts: 23982305851
     client_dur: 1744711
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23982314504
     server_dur: 1722762
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17261,11 +18179,13 @@
     client_ts: 23984133705
     client_dur: 20045
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 23984139858
     server_dur: 7402
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17298,11 +18218,13 @@
     client_ts: 24065542758
     client_dur: 68668
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24065557894
     server_dur: 43859
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17335,11 +18257,13 @@
     client_ts: 24065652205
     client_dur: 1049759
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24065657696
     server_dur: 1032583
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17372,11 +18296,13 @@
     client_ts: 24081125997
     client_dur: 31826
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24081137112
     server_dur: 13041
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17409,11 +18335,13 @@
     client_ts: 24148820113
     client_dur: 68214
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24148833979
     server_dur: 44630
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17446,11 +18374,13 @@
     client_ts: 24148918715
     client_dur: 1009266
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24148923375
     server_dur: 991906
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17483,11 +18413,13 @@
     client_ts: 24150011467
     client_dur: 21064
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24150018449
     server_dur: 7359
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17520,11 +18452,13 @@
     client_ts: 24247778973
     client_dur: 34911
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24247791507
     server_dur: 14684
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17557,11 +18491,13 @@
     client_ts: 24248842389
     client_dur: 66174
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24248855559
     server_dur: 43223
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17594,11 +18530,13 @@
     client_ts: 24248937934
     client_dur: 1130965
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24248943066
     server_dur: 1111247
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17631,11 +18569,13 @@
     client_ts: 24250153754
     client_dur: 21489
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24250161172
     server_dur: 7362
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17668,11 +18608,13 @@
     client_ts: 24332164030
     client_dur: 78622
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24332193334
     server_dur: 39186
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17705,11 +18647,13 @@
     client_ts: 24332271286
     client_dur: 1233475
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24332276288
     server_dur: 1215090
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17742,11 +18686,13 @@
     client_ts: 24350341616
     client_dur: 97785
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24350364644
     server_dur: 50768
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17779,11 +18725,13 @@
     client_ts: 24415506341
     client_dur: 73094
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24415521413
     server_dur: 48860
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17816,11 +18764,13 @@
     client_ts: 24415610395
     client_dur: 1963175
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24415614985
     server_dur: 1944970
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17853,11 +18803,13 @@
     client_ts: 24417688619
     client_dur: 22666
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24417696179
     server_dur: 8868
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17890,11 +18842,13 @@
     client_ts: 24498858408
     client_dur: 72316
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24498874905
     server_dur: 44364
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17927,11 +18881,13 @@
     client_ts: 24498965251
     client_dur: 2212172
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24498971364
     server_dur: 2189623
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -17964,11 +18920,13 @@
     client_ts: 24514530326
     client_dur: 46445
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24514545922
     server_dur: 16967
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18001,11 +18959,13 @@
     client_ts: 24582239101
     client_dur: 102168
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24582262241
     server_dur: 62646
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18038,11 +18998,13 @@
     client_ts: 24582383933
     client_dur: 1125278
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24582393122
     server_dur: 1094530
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18075,11 +19037,13 @@
     client_ts: 24583627699
     client_dur: 36229
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24583638897
     server_dur: 12637
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18112,11 +19076,13 @@
     client_ts: 24665516749
     client_dur: 77492
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24665535013
     server_dur: 46956
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18149,11 +19115,13 @@
     client_ts: 24665629297
     client_dur: 1056190
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24665650779
     server_dur: 1018462
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18186,11 +19154,13 @@
     client_ts: 24681436859
     client_dur: 36053
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24681447493
     server_dur: 17169
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18223,11 +19193,13 @@
     client_ts: 24748868138
     client_dur: 84885
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24748886644
     server_dur: 54090
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18260,11 +19232,13 @@
     client_ts: 24748988510
     client_dur: 1030023
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24748994792
     server_dur: 1007690
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18297,11 +19271,13 @@
     client_ts: 24750114696
     client_dur: 25844
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24750122620
     server_dur: 9559
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18334,11 +19310,13 @@
     client_ts: 24832172264
     client_dur: 71964
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24832186234
     server_dur: 48177
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18371,11 +19349,13 @@
     client_ts: 24832278767
     client_dur: 2319199
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24832284387
     server_dur: 2299722
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18408,11 +19388,13 @@
     client_ts: 24848194066
     client_dur: 38990
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24848207084
     server_dur: 16882
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18445,11 +19427,13 @@
     client_ts: 24915569035
     client_dur: 92319
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24915587307
     server_dur: 61522
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18482,11 +19466,13 @@
     client_ts: 24915703770
     client_dur: 1154960
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24915710935
     server_dur: 1133005
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18519,11 +19505,13 @@
     client_ts: 24916964211
     client_dur: 24607
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24916971111
     server_dur: 10684
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18556,11 +19544,13 @@
     client_ts: 24998945706
     client_dur: 111229
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24998970836
     server_dur: 66036
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18593,11 +19583,13 @@
     client_ts: 24999109626
     client_dur: 1079778
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 24999121401
     server_dur: 1042002
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18630,11 +19622,13 @@
     client_ts: 25014488039
     client_dur: 70943
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25014504840
     server_dur: 24296
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18667,11 +19661,13 @@
     client_ts: 25082202078
     client_dur: 73854
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25082217860
     server_dur: 47920
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18704,11 +19700,13 @@
     client_ts: 25082306875
     client_dur: 1782258
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25082312116
     server_dur: 1764384
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18741,11 +19739,13 @@
     client_ts: 25084166020
     client_dur: 20222
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25084172595
     server_dur: 7394
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18778,11 +19778,13 @@
     client_ts: 25165504670
     client_dur: 81307
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25165518960
     server_dur: 55941
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18815,11 +19817,13 @@
     client_ts: 25165619958
     client_dur: 997114
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25165625294
     server_dur: 978831
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18852,11 +19856,13 @@
     client_ts: 25181108704
     client_dur: 38312
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25181120018
     server_dur: 17603
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18889,11 +19895,13 @@
     client_ts: 25248839993
     client_dur: 71834
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25248855062
     server_dur: 47208
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18926,11 +19934,13 @@
     client_ts: 25248942032
     client_dur: 1065822
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25248946700
     server_dur: 1047977
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -18963,11 +19973,13 @@
     client_ts: 25250092465
     client_dur: 21121
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25250099244
     server_dur: 7872
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19000,11 +20012,13 @@
     client_ts: 25332433123
     client_dur: 129185
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25332463293
     server_dur: 75363
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19037,11 +20051,13 @@
     client_ts: 25332622145
     client_dur: 3970213
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25332636462
     server_dur: 3919134
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19074,11 +20090,13 @@
     client_ts: 25347937130
     client_dur: 63143
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25347956107
     server_dur: 23630
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19111,11 +20129,13 @@
     client_ts: 25415532955
     client_dur: 74958
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25415548486
     server_dur: 48910
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19148,11 +20168,13 @@
     client_ts: 25415639063
     client_dur: 1045725
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25415643854
     server_dur: 1028657
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19185,11 +20207,13 @@
     client_ts: 25416779828
     client_dur: 22459
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25416786530
     server_dur: 8393
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19222,11 +20246,13 @@
     client_ts: 25499090883
     client_dur: 135524
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25499122517
     server_dur: 77879
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19259,11 +20285,13 @@
     client_ts: 25499287661
     client_dur: 1179689
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25499301640
     server_dur: 1130895
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19296,11 +20324,13 @@
     client_ts: 25514592061
     client_dur: 62604
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25514612449
     server_dur: 22109
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19333,11 +20363,13 @@
     client_ts: 25582338627
     client_dur: 162705
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25582368275
     server_dur: 92253
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19370,11 +20402,13 @@
     client_ts: 25582559609
     client_dur: 1051443
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25582572935
     server_dur: 992794
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19407,11 +20441,13 @@
     client_ts: 25583792668
     client_dur: 57270
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25583811204
     server_dur: 18520
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19444,11 +20480,13 @@
     client_ts: 25665575887
     client_dur: 98551
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25665594994
     server_dur: 67512
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19481,11 +20519,13 @@
     client_ts: 25665714191
     client_dur: 2056246
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25665719790
     server_dur: 2036405
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19518,11 +20558,13 @@
     client_ts: 25681400816
     client_dur: 89610
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25681427461
     server_dur: 37612
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19555,11 +20597,13 @@
     client_ts: 25748935127
     client_dur: 126067
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25748964935
     server_dur: 75428
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19592,11 +20636,13 @@
     client_ts: 25749129537
     client_dur: 1130001
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25749140148
     server_dur: 1092654
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19629,11 +20675,13 @@
     client_ts: 25750403211
     client_dur: 43991
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25750417428
     server_dur: 15659
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19666,11 +20714,13 @@
     client_ts: 25832201262
     client_dur: 76032
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25832217990
     server_dur: 49551
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19703,11 +20753,13 @@
     client_ts: 25832312368
     client_dur: 1052337
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25832317105
     server_dur: 1035144
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19740,11 +20792,13 @@
     client_ts: 25847799175
     client_dur: 40678
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25847812958
     server_dur: 17906
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19777,11 +20831,13 @@
     client_ts: 25855749248
     client_dur: 31727
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25855758456
     server_dur: 14151
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19814,11 +20870,13 @@
     client_ts: 25865415841
     client_dur: 46578
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25865424168
     server_dur: 29991
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19851,11 +20909,13 @@
     client_ts: 25865488539
     client_dur: 1091161
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25865493112
     server_dur: 1074785
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19888,11 +20948,13 @@
     client_ts: 25882229417
     client_dur: 49798
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25882240791
     server_dur: 29545
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19925,11 +20987,13 @@
     client_ts: 25882302427
     client_dur: 976002
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25882306957
     server_dur: 960192
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19962,11 +21026,13 @@
     client_ts: 25915516257
     client_dur: 67861
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25915531128
     server_dur: 43477
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -19999,11 +21065,13 @@
     client_ts: 25915614740
     client_dur: 879798
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25915619820
     server_dur: 861862
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20036,11 +21104,13 @@
     client_ts: 25947791827
     client_dur: 36462
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25947803365
     server_dur: 16366
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20073,11 +21143,13 @@
     client_ts: 25965634137
     client_dur: 62809
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25965647687
     server_dur: 39248
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20110,11 +21182,13 @@
     client_ts: 25965727363
     client_dur: 1082911
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25965732380
     server_dur: 1065715
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20147,11 +21221,13 @@
     client_ts: 25966880090
     client_dur: 18647
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25966885882
     server_dur: 6742
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20184,11 +21260,13 @@
     client_ts: 25998857609
     client_dur: 68802
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25998873383
     server_dur: 43179
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20221,11 +21299,13 @@
     client_ts: 25998956453
     client_dur: 896029
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 25998961673
     server_dur: 878692
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20258,11 +21338,13 @@
     client_ts: 26064480113
     client_dur: 41059
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26064494262
     server_dur: 17230
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20295,11 +21377,13 @@
     client_ts: 26082167007
     client_dur: 68814
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26082181896
     server_dur: 44307
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20332,11 +21416,13 @@
     client_ts: 26082265345
     client_dur: 984160
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26082270124
     server_dur: 967726
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20369,11 +21455,13 @@
     client_ts: 26083328041
     client_dur: 20356
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26083334137
     server_dur: 7873
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20406,11 +21494,13 @@
     client_ts: 26165543672
     client_dur: 76748
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26165564279
     server_dur: 46139
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20443,11 +21533,13 @@
     client_ts: 26165664730
     client_dur: 952016
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26165670566
     server_dur: 934833
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20480,11 +21572,13 @@
     client_ts: 26181134030
     client_dur: 39663
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26181147708
     server_dur: 17312
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20517,11 +21611,13 @@
     client_ts: 26265553005
     client_dur: 106037
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26265570929
     server_dur: 76133
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20554,11 +21650,13 @@
     client_ts: 26265695797
     client_dur: 1070288
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26265701257
     server_dur: 1051768
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20591,11 +21689,13 @@
     client_ts: 26266851930
     client_dur: 21080
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26266858935
     server_dur: 7493
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20628,11 +21728,13 @@
     client_ts: 26348993760
     client_dur: 74635
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26349012914
     server_dur: 45210
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20665,11 +21767,13 @@
     client_ts: 26349100408
     client_dur: 985507
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26349105368
     server_dur: 967525
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20702,11 +21806,13 @@
     client_ts: 26364884926
     client_dur: 48465
     client_tid: 496
+    client_pid: 496
     server_process: "/vendor/bin/hw/android.hardware.graphics.composer3-service.ranchu"
     server_thread: "binder:446_1"
     server_ts: 26364907480
     server_dur: 16844
     server_tid: 507
+    server_pid: 446
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20739,11 +21845,13 @@
     client_ts: 25527594973
     client_dur: 961513
     client_tid: 431
+    client_pid: 431
     server_process: "system_server"
     server_thread: "system_server"
     server_ts: 25528486848
     server_dur: 41164
     server_tid: 641
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20776,11 +21884,13 @@
     client_ts: 25519230900
     client_dur: 67687
     client_tid: 458
+    client_pid: 458
     server_process: "system_server"
     server_thread: "system-server-i"
     server_ts: 25519257217
     server_dur: 19303
     server_tid: 665
+    server_pid: 641
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20813,11 +21923,13 @@
     client_ts: 25887934200
     client_dur: 73511
     client_tid: 1625
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.input.processor-service.example"
     server_thread: "android.hardwar"
     server_ts: 25887963950
     server_dur: 31341
     server_tid: 447
+    server_pid: 447
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20850,11 +21962,13 @@
     client_ts: 25924014206
     client_dur: 48397
     client_tid: 1625
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.input.processor-service.example"
     server_thread: "android.hardwar"
     server_ts: 25924032607
     server_dur: 19471
     server_tid: 447
+    server_pid: 447
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20887,11 +22001,13 @@
     client_ts: 25924552433
     client_dur: 51388
     client_tid: 1625
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.input.processor-service.example"
     server_thread: "android.hardwar"
     server_ts: 25924572649
     server_dur: 16951
     server_tid: 447
+    server_pid: 447
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20924,11 +22040,13 @@
     client_ts: 25925236390
     client_dur: 40800
     client_tid: 1625
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.input.processor-service.example"
     server_thread: "android.hardwar"
     server_ts: 25925252802
     server_dur: 11692
     server_tid: 447
+    server_pid: 447
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20961,11 +22079,13 @@
     client_ts: 25925312006
     client_dur: 22098
     client_tid: 1625
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.input.processor-service.example"
     server_thread: "android.hardwar"
     server_ts: 25925321018
     server_dur: 4977
     server_tid: 447
+    server_pid: 447
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -20998,11 +22118,13 @@
     client_ts: 25925340685
     client_dur: 18485
     client_tid: 1625
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.input.processor-service.example"
     server_thread: "android.hardwar"
     server_ts: 25925348223
     server_dur: 3660
     server_tid: 447
+    server_pid: 447
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21035,11 +22157,13 @@
     client_ts: 25925364763
     client_dur: 18634
     client_tid: 1625
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.input.processor-service.example"
     server_thread: "android.hardwar"
     server_ts: 25925372247
     server_dur: 3490
     server_tid: 447
+    server_pid: 447
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21072,11 +22196,13 @@
     client_ts: 26046721012
     client_dur: 137219
     client_tid: 1625
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.input.processor-service.example"
     server_thread: "android.hardwar"
     server_ts: 26046826730
     server_dur: 20407
     server_tid: 447
+    server_pid: 447
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21109,11 +22235,13 @@
     client_ts: 25848902022
     client_dur: 109438
     client_tid: 662
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25848918031
     server_dur: 71184
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21147,11 +22275,13 @@
     client_ts: 25849035817
     client_dur: 85138
     client_tid: 662
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25849046771
     server_dur: 23591
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21184,11 +22314,13 @@
     client_ts: 25965522657
     client_dur: 87636
     client_tid: 662
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25965536704
     server_dur: 31166
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -21221,11 +22353,13 @@
     client_ts: 26046475353
     client_dur: 139999
     client_tid: 662
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 26046494430
     server_dur: 36023
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21252,11 +22386,13 @@
     client_ts: 26049659202
     client_dur: 66793
     client_tid: 662
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 26049676304
     server_dur: 21699
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21283,11 +22419,13 @@
     client_ts: 25852597933
     client_dur: 72360
     client_tid: 663
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25852614647
     server_dur: 40436
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21321,11 +22459,13 @@
     client_ts: 25852691734
     client_dur: 40316
     client_tid: 663
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25852702316
     server_dur: 18235
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21358,11 +22498,13 @@
     client_ts: 25851140935
     client_dur: 82845
     client_tid: 661
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25851157134
     server_dur: 45749
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21396,11 +22538,13 @@
     client_ts: 25851245190
     client_dur: 40441
     client_tid: 661
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25851255329
     server_dur: 18715
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21434,11 +22578,13 @@
     client_ts: 25854136251
     client_dur: 140850
     client_tid: 661
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25854153001
     server_dur: 42359
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21471,11 +22617,13 @@
     client_ts: 25982476503
     client_dur: 71952
     client_tid: 660
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25982503060
     server_dur: 29743
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21508,11 +22656,13 @@
     client_ts: 25873131715
     client_dur: 117082
     client_tid: 659
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25873153628
     server_dur: 22969
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21539,11 +22689,13 @@
     client_ts: 25883734665
     client_dur: 152980
     client_tid: 659
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25883839992
     server_dur: 25887
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21576,11 +22728,13 @@
     client_ts: 25537922715
     client_dur: 260041
     client_tid: 1600
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.lights-service.example"
     server_thread: "android.hardwar"
     server_ts: 25537947459
     server_dur: 137623
     server_tid: 448
+    server_pid: 448
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21608,11 +22762,13 @@
     client_ts: 25285800698
     client_dur: 1963972
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25285972364
     server_dur: 1764197
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -21670,11 +22826,13 @@
     client_ts: 25359359930
     client_dur: 58056845
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25359406757
     server_dur: 57997245
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -21726,11 +22884,13 @@
     client_ts: 25417583831
     client_dur: 159127
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25417600127
     server_dur: 117766
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21764,11 +22924,13 @@
     client_ts: 25417795254
     client_dur: 84143
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25417807548
     server_dur: 52980
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21802,11 +22964,13 @@
     client_ts: 25417900256
     client_dur: 72685
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25417909455
     server_dur: 47502
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21840,11 +23004,13 @@
     client_ts: 25417989778
     client_dur: 70644
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25418000174
     server_dur: 44675
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21878,11 +23044,13 @@
     client_ts: 25418084967
     client_dur: 63335
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25418094122
     server_dur: 43952
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21916,11 +23084,13 @@
     client_ts: 25418162868
     client_dur: 73089
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25418174366
     server_dur: 51193
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21954,11 +23124,13 @@
     client_ts: 25418257945
     client_dur: 71217
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25418268205
     server_dur: 50582
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -21992,11 +23164,13 @@
     client_ts: 25418344237
     client_dur: 68933
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25418354448
     server_dur: 48033
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22030,11 +23204,13 @@
     client_ts: 25418434003
     client_dur: 70051
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25418444322
     server_dur: 49218
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22068,11 +23244,13 @@
     client_ts: 25418523244
     client_dur: 71860
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25418533528
     server_dur: 51097
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22106,11 +23284,13 @@
     client_ts: 25418613235
     client_dur: 80217
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25418623429
     server_dur: 59922
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22144,11 +23324,13 @@
     client_ts: 25418707467
     client_dur: 100848
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25418716983
     server_dur: 80176
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22182,11 +23364,13 @@
     client_ts: 25418859801
     client_dur: 96287
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25418874922
     server_dur: 66129
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22220,11 +23404,13 @@
     client_ts: 25418990456
     client_dur: 73140
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419002227
     server_dur: 50140
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22258,11 +23444,13 @@
     client_ts: 25419082851
     client_dur: 69691
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419093189
     server_dur: 48614
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22296,11 +23484,13 @@
     client_ts: 25419197218
     client_dur: 97019
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419208385
     server_dur: 75308
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22334,11 +23524,13 @@
     client_ts: 25419309331
     client_dur: 68503
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419319559
     server_dur: 48070
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22372,11 +23564,13 @@
     client_ts: 25419402979
     client_dur: 68957
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419413041
     server_dur: 48592
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22410,11 +23604,13 @@
     client_ts: 25419490548
     client_dur: 70455
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419500948
     server_dur: 49673
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22448,11 +23644,13 @@
     client_ts: 25419576883
     client_dur: 68126
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419587154
     server_dur: 47213
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22486,11 +23684,13 @@
     client_ts: 25419662926
     client_dur: 68985
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419673103
     server_dur: 48313
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22524,11 +23724,13 @@
     client_ts: 25419747914
     client_dur: 70204
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419758511
     server_dur: 49425
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22562,11 +23764,13 @@
     client_ts: 25419838530
     client_dur: 74294
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419851530
     server_dur: 50943
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22600,11 +23804,13 @@
     client_ts: 25419930878
     client_dur: 71254
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25419941349
     server_dur: 50447
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22638,11 +23844,13 @@
     client_ts: 25420022065
     client_dur: 70064
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420034446
     server_dur: 47514
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22676,11 +23884,13 @@
     client_ts: 25420107549
     client_dur: 67997
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420117365
     server_dur: 47959
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22714,11 +23924,13 @@
     client_ts: 25420191395
     client_dur: 67798
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420201429
     server_dur: 47599
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22752,11 +23964,13 @@
     client_ts: 25420275173
     client_dur: 68378
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420285912
     server_dur: 47561
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22790,11 +24004,13 @@
     client_ts: 25420359212
     client_dur: 68447
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420369692
     server_dur: 47721
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22828,11 +24044,13 @@
     client_ts: 25420458125
     client_dur: 69217
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420468424
     server_dur: 48683
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22866,11 +24084,13 @@
     client_ts: 25420542827
     client_dur: 68982
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420553053
     server_dur: 48593
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22904,11 +24124,13 @@
     client_ts: 25420642092
     client_dur: 75336
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420652418
     server_dur: 54692
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22942,11 +24164,13 @@
     client_ts: 25420732600
     client_dur: 66989
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420742486
     server_dur: 46762
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -22980,11 +24204,13 @@
     client_ts: 25420814670
     client_dur: 66733
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420824622
     server_dur: 46663
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23018,11 +24244,13 @@
     client_ts: 25420898454
     client_dur: 67283
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420908490
     server_dur: 47258
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23056,11 +24284,13 @@
     client_ts: 25420980515
     client_dur: 68119
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25420990555
     server_dur: 47881
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23094,11 +24324,13 @@
     client_ts: 25421062960
     client_dur: 67691
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25421073099
     server_dur: 47279
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23132,11 +24364,13 @@
     client_ts: 25421149398
     client_dur: 67456
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25421159582
     server_dur: 46812
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23170,11 +24404,13 @@
     client_ts: 25421234249
     client_dur: 102474
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25421244402
     server_dur: 81689
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23208,11 +24444,13 @@
     client_ts: 25421351001
     client_dur: 68629
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25421361736
     server_dur: 50268
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23246,11 +24484,13 @@
     client_ts: 25421437438
     client_dur: 78505
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25421447770
     server_dur: 60705
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23284,11 +24524,13 @@
     client_ts: 25421529017
     client_dur: 90619
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25421536869
     server_dur: 70139
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23322,11 +24564,13 @@
     client_ts: 25421667924
     client_dur: 93173
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25421681338
     server_dur: 67854
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23360,11 +24604,13 @@
     client_ts: 25421792054
     client_dur: 79868
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25421807359
     server_dur: 55975
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23398,11 +24644,13 @@
     client_ts: 25421889199
     client_dur: 85495
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25421897198
     server_dur: 65834
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23436,11 +24684,13 @@
     client_ts: 25422008441
     client_dur: 90387
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25422024828
     server_dur: 62034
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23474,11 +24724,13 @@
     client_ts: 25422133767
     client_dur: 75807
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25422147676
     server_dur: 51244
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23512,11 +24764,13 @@
     client_ts: 25422225432
     client_dur: 98464
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25422233734
     server_dur: 79168
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23550,11 +24804,13 @@
     client_ts: 25422355100
     client_dur: 93353
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25422370044
     server_dur: 64908
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23588,11 +24844,13 @@
     client_ts: 25422488580
     client_dur: 81077
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25422503734
     server_dur: 56902
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23626,11 +24884,13 @@
     client_ts: 25422585669
     client_dur: 81637
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25422593474
     server_dur: 62945
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23664,11 +24924,13 @@
     client_ts: 25422698364
     client_dur: 307058
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25422912325
     server_dur: 72852
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23702,11 +24964,13 @@
     client_ts: 25423038416
     client_dur: 85760
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25423050636
     server_dur: 59617
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23740,11 +25004,13 @@
     client_ts: 25423141970
     client_dur: 65309
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25423147589
     server_dur: 46373
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23778,11 +25044,13 @@
     client_ts: 25423222741
     client_dur: 83982
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25423241349
     server_dur: 52179
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23816,11 +25084,13 @@
     client_ts: 25423321767
     client_dur: 84883
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25423330170
     server_dur: 64541
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23854,11 +25124,13 @@
     client_ts: 25423444205
     client_dur: 97794
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25423460614
     server_dur: 64145
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23892,11 +25164,13 @@
     client_ts: 25423571069
     client_dur: 79325
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25423585310
     server_dur: 56042
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23930,11 +25204,13 @@
     client_ts: 25423667089
     client_dur: 80196
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25423674807
     server_dur: 61267
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -23968,11 +25244,13 @@
     client_ts: 25423778912
     client_dur: 85732
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25423789294
     server_dur: 63712
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24006,11 +25284,13 @@
     client_ts: 25423899033
     client_dur: 83874
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25423913207
     server_dur: 61126
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24044,11 +25324,13 @@
     client_ts: 25423999018
     client_dur: 80807
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25424006723
     server_dur: 62351
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24082,11 +25364,13 @@
     client_ts: 25424119494
     client_dur: 90723
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25424133267
     server_dur: 65314
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24120,11 +25404,13 @@
     client_ts: 25424244832
     client_dur: 75424
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25424257485
     server_dur: 54108
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24158,11 +25444,13 @@
     client_ts: 25424340551
     client_dur: 68134
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25424352371
     server_dur: 47952
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24196,11 +25484,13 @@
     client_ts: 25424423268
     client_dur: 79235
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25424430889
     server_dur: 60700
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24234,11 +25524,13 @@
     client_ts: 25424532071
     client_dur: 132824
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25424548055
     server_dur: 63569
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24272,11 +25564,13 @@
     client_ts: 25424695404
     client_dur: 78895
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25424709439
     server_dur: 55753
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24310,11 +25604,13 @@
     client_ts: 25424790712
     client_dur: 84106
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25424798759
     server_dur: 65429
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24348,11 +25644,13 @@
     client_ts: 25424904112
     client_dur: 86631
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_3"
     server_ts: 25424916724
     server_dur: 62466
     server_tid: 1590
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24386,11 +25684,13 @@
     client_ts: 25425012627
     client_dur: 82418
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25425020923
     server_dur: 61511
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24424,11 +25724,13 @@
     client_ts: 25425134890
     client_dur: 80492
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25425151484
     server_dur: 55304
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24462,11 +25764,13 @@
     client_ts: 25425231522
     client_dur: 103387
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25425239219
     server_dur: 85661
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -24506,11 +25810,13 @@
     client_ts: 25425372618
     client_dur: 91185
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25425384448
     server_dur: 68305
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24544,11 +25850,13 @@
     client_ts: 25425498321
     client_dur: 82574
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25425511999
     server_dur: 60168
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24582,11 +25890,13 @@
     client_ts: 25425597030
     client_dur: 96570
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25425604832
     server_dur: 77508
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24620,11 +25930,13 @@
     client_ts: 25425734906
     client_dur: 90504
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25425748243
     server_dur: 65377
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24658,11 +25970,13 @@
     client_ts: 25425853178
     client_dur: 75664
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25425868250
     server_dur: 50805
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24696,11 +26010,13 @@
     client_ts: 25425949096
     client_dur: 75914
     client_tid: 1591
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25425967109
     server_dur: 49564
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24733,11 +26049,13 @@
     client_ts: 25507770941
     client_dur: 659345
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25507817302
     server_dur: 493959
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24764,11 +26082,13 @@
     client_ts: 25508483480
     client_dur: 113431
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25508520735
     server_dur: 37596
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24795,11 +26115,13 @@
     client_ts: 25512346526
     client_dur: 247626
     client_tid: 665
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.sensors-service.example"
     server_thread: "android.hardwar"
     server_ts: 25512379433
     server_dur: 68665
     server_tid: 458
+    server_pid: 458
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24832,11 +26154,13 @@
     client_ts: 25519832521
     client_dur: 244039
     client_tid: 665
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.sensors-service.example"
     server_thread: "android.hardwar"
     server_ts: 25519857925
     server_dur: 186450
     server_tid: 458
+    server_pid: 458
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24863,11 +26187,13 @@
     client_ts: 25520140841
     client_dur: 73832
     client_tid: 665
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.sensors-service.example"
     server_thread: "android.hardwar"
     server_ts: 25520164682
     server_dur: 18571
     server_tid: 458
+    server_pid: 458
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24894,11 +26220,13 @@
     client_ts: 25523026906
     client_dur: 340409
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25523191948
     server_dur: 40297
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24931,11 +26259,13 @@
     client_ts: 25524188687
     client_dur: 89696
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25524226273
     server_dur: 31748
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -24968,11 +26298,13 @@
     client_ts: 25524630643
     client_dur: 80450
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25524666614
     server_dur: 25632
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25005,11 +26337,13 @@
     client_ts: 25524881306
     client_dur: 64099
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25524906781
     server_dur: 18982
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25042,11 +26376,13 @@
     client_ts: 25525155622
     client_dur: 182063
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25525206343
     server_dur: 111535
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25079,11 +26415,13 @@
     client_ts: 25886972081
     client_dur: 392512
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/hwservicemanager"
     server_thread: "hwservicemanage"
     server_ts: 25887243579
     server_dur: 100309
     server_tid: 247
+    server_pid: 247
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25116,11 +26454,13 @@
     client_ts: 25887384160
     client_dur: 146196
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/hwservicemanager"
     server_thread: "hwservicemanage"
     server_ts: 25887501006
     server_dur: 15631
     server_tid: 247
+    server_pid: 247
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25153,11 +26493,13 @@
     client_ts: 25887600584
     client_dur: 263828
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/hwservicemanager"
     server_thread: "hwservicemanage"
     server_ts: 25887779478
     server_dur: 69332
     server_tid: 247
+    server_pid: 247
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25190,11 +26532,13 @@
     client_ts: 25888119355
     client_dur: 387727
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25888158284
     server_dur: 330571
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25221,11 +26565,13 @@
     client_ts: 25888835343
     client_dur: 324156
     client_tid: 665
+    client_pid: 641
     server_process: "/system/bin/hwservicemanager"
     server_thread: "hwservicemanage"
     server_ts: 25888991319
     server_dur: 112375
     server_tid: 247
+    server_pid: 247
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25258,11 +26604,13 @@
     client_ts: 24562258099
     client_dur: 95711
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24562294551
     server_dur: 25989
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25295,11 +26643,13 @@
     client_ts: 24576142302
     client_dur: 100932
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24576178251
     server_dur: 35440
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25326,11 +26676,13 @@
     client_ts: 24577928034
     client_dur: 98898
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24577965065
     server_dur: 33433
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25357,11 +26709,13 @@
     client_ts: 24579825258
     client_dur: 91999
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24579857006
     server_dur: 32771
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25388,11 +26742,13 @@
     client_ts: 24586203617
     client_dur: 160861
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24586241455
     server_dur: 29609
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25425,11 +26781,13 @@
     client_ts: 24588923637
     client_dur: 381582
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24588962975
     server_dur: 29095
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25462,11 +26820,13 @@
     client_ts: 24592040006
     client_dur: 240562
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24592076186
     server_dur: 28582
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25499,11 +26859,13 @@
     client_ts: 24594213703
     client_dur: 673648
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24594243436
     server_dur: 22881
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25536,11 +26898,13 @@
     client_ts: 24595958354
     client_dur: 77980
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24595993807
     server_dur: 22950
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25573,11 +26937,13 @@
     client_ts: 24597520537
     client_dur: 49553
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24597542932
     server_dur: 16294
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25610,11 +26976,13 @@
     client_ts: 24598897106
     client_dur: 68689
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24598922372
     server_dur: 18191
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25647,11 +27015,13 @@
     client_ts: 24600413274
     client_dur: 57521
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24600434769
     server_dur: 13168
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25684,11 +27054,13 @@
     client_ts: 24602399966
     client_dur: 80715
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24602431540
     server_dur: 23433
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25721,11 +27093,13 @@
     client_ts: 24603839586
     client_dur: 50942
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24603864064
     server_dur: 11482
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25758,11 +27132,13 @@
     client_ts: 24605137324
     client_dur: 53391
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24605161847
     server_dur: 16108
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25795,11 +27171,13 @@
     client_ts: 24607833798
     client_dur: 79225
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24607864767
     server_dur: 25864
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25832,11 +27210,13 @@
     client_ts: 24609098413
     client_dur: 86504
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24609132077
     server_dur: 29462
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25869,11 +27249,13 @@
     client_ts: 24646418600
     client_dur: 104600
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24646446859
     server_dur: 25546
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25900,11 +27282,13 @@
     client_ts: 24671385803
     client_dur: 170289
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24671409853
     server_dur: 37825
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25931,11 +27315,13 @@
     client_ts: 24681379300
     client_dur: 57187
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24681399705
     server_dur: 21305
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25962,11 +27348,13 @@
     client_ts: 24682251210
     client_dur: 182307
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24682273023
     server_dur: 20664
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -25993,11 +27381,13 @@
     client_ts: 24683246275
     client_dur: 207024
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24683267459
     server_dur: 20968
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26024,11 +27414,13 @@
     client_ts: 24685075466
     client_dur: 189680
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24685097014
     server_dur: 20394
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26055,11 +27447,13 @@
     client_ts: 24687047835
     client_dur: 52825
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24687065149
     server_dur: 20404
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26086,11 +27480,13 @@
     client_ts: 24690752961
     client_dur: 1138197
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24690773594
     server_dur: 20275
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26117,11 +27513,13 @@
     client_ts: 24692956765
     client_dur: 967152
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24692973830
     server_dur: 19601
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26148,11 +27546,13 @@
     client_ts: 24699086985
     client_dur: 2761757
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24699105127
     server_dur: 19717
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26191,11 +27591,13 @@
     client_ts: 24704026795
     client_dur: 151503
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24704048621
     server_dur: 19875
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26222,11 +27624,13 @@
     client_ts: 24707823403
     client_dur: 418749
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24707844258
     server_dur: 19420
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26253,11 +27657,13 @@
     client_ts: 24715413797
     client_dur: 55453
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24715432924
     server_dur: 20515
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26284,11 +27690,13 @@
     client_ts: 24718094584
     client_dur: 396933
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24718112985
     server_dur: 20123
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26315,11 +27723,13 @@
     client_ts: 24720389105
     client_dur: 56955
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24720410588
     server_dur: 19497
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26346,11 +27756,13 @@
     client_ts: 24722237372
     client_dur: 57988
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24722254479
     server_dur: 19636
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26377,11 +27789,13 @@
     client_ts: 24724223102
     client_dur: 52789
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24724240486
     server_dur: 19628
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26408,11 +27822,13 @@
     client_ts: 24727963525
     client_dur: 54516
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24727983399
     server_dur: 19277
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26439,11 +27855,13 @@
     client_ts: 24729275294
     client_dur: 69527
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24729308987
     server_dur: 19817
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26470,11 +27888,13 @@
     client_ts: 24730464621
     client_dur: 56830
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24730486611
     server_dur: 19083
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26501,11 +27921,13 @@
     client_ts: 24731541792
     client_dur: 59775
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24731563743
     server_dur: 19533
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26532,11 +27954,13 @@
     client_ts: 24732635124
     client_dur: 62661
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24732657580
     server_dur: 22874
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26563,11 +27987,13 @@
     client_ts: 24733694931
     client_dur: 58749
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24733718525
     server_dur: 19626
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26594,11 +28020,13 @@
     client_ts: 24734519268
     client_dur: 56933
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24734541289
     server_dur: 19505
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26625,11 +28053,13 @@
     client_ts: 24735621739
     client_dur: 59911
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24735644076
     server_dur: 19698
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26656,11 +28086,13 @@
     client_ts: 24737578050
     client_dur: 70375
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24737599805
     server_dur: 19338
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26687,11 +28119,13 @@
     client_ts: 24738379435
     client_dur: 59307
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24738401334
     server_dur: 19247
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26718,11 +28152,13 @@
     client_ts: 24739381820
     client_dur: 59860
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24739404070
     server_dur: 19274
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26749,11 +28185,13 @@
     client_ts: 24740296188
     client_dur: 60137
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24740318642
     server_dur: 19217
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26780,11 +28218,13 @@
     client_ts: 24741062753
     client_dur: 60144
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24741084854
     server_dur: 19235
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26811,11 +28251,13 @@
     client_ts: 24742188157
     client_dur: 62717
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24742210951
     server_dur: 22644
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26842,11 +28284,13 @@
     client_ts: 24743215720
     client_dur: 57215
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24743238157
     server_dur: 19299
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26873,11 +28317,13 @@
     client_ts: 24744310059
     client_dur: 62304
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24744332347
     server_dur: 22570
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26904,11 +28350,13 @@
     client_ts: 24745726015
     client_dur: 42535
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24745741462
     server_dur: 13274
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26935,11 +28383,13 @@
     client_ts: 24746812940
     client_dur: 62487
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24746836091
     server_dur: 21679
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26966,11 +28416,13 @@
     client_ts: 24747614390
     client_dur: 60851
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24747636567
     server_dur: 21322
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -26997,11 +28449,13 @@
     client_ts: 24748641464
     client_dur: 98796
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24748705431
     server_dur: 19737
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27028,11 +28482,13 @@
     client_ts: 24749789307
     client_dur: 58174
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24749812809
     server_dur: 19265
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27059,11 +28515,13 @@
     client_ts: 24753793235
     client_dur: 285781
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24753872138
     server_dur: 21667
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27090,11 +28548,13 @@
     client_ts: 24760573761
     client_dur: 55871
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24760593814
     server_dur: 19818
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27121,11 +28581,13 @@
     client_ts: 24761811419
     client_dur: 104650
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24761830688
     server_dur: 19717
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27152,11 +28614,13 @@
     client_ts: 24763891343
     client_dur: 52306
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24763908905
     server_dur: 19517
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27183,11 +28647,13 @@
     client_ts: 24764906537
     client_dur: 55552
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24764927197
     server_dur: 19435
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27214,11 +28680,13 @@
     client_ts: 24766255714
     client_dur: 69186
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24766285051
     server_dur: 22399
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27245,11 +28713,13 @@
     client_ts: 24767227873
     client_dur: 55587
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24767248556
     server_dur: 19425
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27276,11 +28746,13 @@
     client_ts: 24770081943
     client_dur: 54467
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24770101200
     server_dur: 19938
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27307,11 +28779,13 @@
     client_ts: 24773596028
     client_dur: 197569
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24773614039
     server_dur: 28282
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27338,11 +28812,13 @@
     client_ts: 24785387433
     client_dur: 2028794
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24785409117
     server_dur: 20078
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27369,11 +28845,13 @@
     client_ts: 24788209976
     client_dur: 153903
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24788233234
     server_dur: 20622
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27400,11 +28878,13 @@
     client_ts: 24789221986
     client_dur: 57706
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24789245188
     server_dur: 19214
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27431,11 +28911,13 @@
     client_ts: 24790705709
     client_dur: 65167
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24790730301
     server_dur: 25107
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27462,11 +28944,13 @@
     client_ts: 24791500958
     client_dur: 57742
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24791523460
     server_dur: 19471
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27493,11 +28977,13 @@
     client_ts: 24792307538
     client_dur: 58314
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24792330556
     server_dur: 19368
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27524,11 +29010,13 @@
     client_ts: 24793372795
     client_dur: 57390
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24793395000
     server_dur: 19464
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27555,11 +29043,13 @@
     client_ts: 24794492101
     client_dur: 54114
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24794511315
     server_dur: 19523
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27586,11 +29076,13 @@
     client_ts: 24795410099
     client_dur: 54987
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24795430573
     server_dur: 19258
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27617,11 +29109,13 @@
     client_ts: 24796677839
     client_dur: 55067
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24796698242
     server_dur: 19086
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27648,11 +29142,13 @@
     client_ts: 24797815386
     client_dur: 53079
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24797833699
     server_dur: 19213
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27679,11 +29175,13 @@
     client_ts: 24799449503
     client_dur: 56617
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24799470930
     server_dur: 19513
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27710,11 +29208,13 @@
     client_ts: 24805536696
     client_dur: 49858
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24805554922
     server_dur: 16943
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27741,11 +29241,13 @@
     client_ts: 24808769073
     client_dur: 53981
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24808788665
     server_dur: 19040
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27772,11 +29274,13 @@
     client_ts: 24821382491
     client_dur: 48977
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24821399252
     server_dur: 17308
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27803,11 +29307,13 @@
     client_ts: 24825799132
     client_dur: 58481
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24825818665
     server_dur: 20241
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27834,11 +29340,13 @@
     client_ts: 24836015985
     client_dur: 60190
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24836036269
     server_dur: 24240
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27865,11 +29373,13 @@
     client_ts: 24838902949
     client_dur: 155997
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24838939369
     server_dur: 21040
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27902,11 +29412,13 @@
     client_ts: 24842361861
     client_dur: 70409
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24842390226
     server_dur: 21512
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27939,11 +29451,13 @@
     client_ts: 24849556375
     client_dur: 183165
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24849690991
     server_dur: 24294
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -27976,11 +29490,13 @@
     client_ts: 24871639354
     client_dur: 68462
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24871671014
     server_dur: 22834
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28013,11 +29529,13 @@
     client_ts: 24873845755
     client_dur: 1096098
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24873871989
     server_dur: 21071
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28044,11 +29562,13 @@
     client_ts: 24877992364
     client_dur: 187259
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24878014368
     server_dur: 20564
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28081,11 +29601,13 @@
     client_ts: 24881598747
     client_dur: 82762
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24881620007
     server_dur: 37794
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28118,11 +29640,13 @@
     client_ts: 24882762008
     client_dur: 343179
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24883063470
     server_dur: 18976
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28155,11 +29679,13 @@
     client_ts: 24884363797
     client_dur: 138909
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24884469472
     server_dur: 19017
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28192,11 +29718,13 @@
     client_ts: 24886610735
     client_dur: 64238
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24886632996
     server_dur: 20186
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28229,11 +29757,13 @@
     client_ts: 24888078440
     client_dur: 422798
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24888463594
     server_dur: 19313
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28266,11 +29796,13 @@
     client_ts: 24891297979
     client_dur: 58221
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24891321099
     server_dur: 19916
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28303,11 +29835,13 @@
     client_ts: 24893930550
     client_dur: 54133
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24893950723
     server_dur: 20568
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28340,11 +29874,13 @@
     client_ts: 24896595580
     client_dur: 67589
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24896624063
     server_dur: 20320
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28377,11 +29913,13 @@
     client_ts: 24903024474
     client_dur: 206934
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24903154456
     server_dur: 20235
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28414,11 +29952,13 @@
     client_ts: 24915150978
     client_dur: 62178
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24915179748
     server_dur: 21871
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28451,11 +29991,13 @@
     client_ts: 24921237169
     client_dur: 1189989
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24921585066
     server_dur: 22945
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28488,11 +30030,13 @@
     client_ts: 24924342216
     client_dur: 260104
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24924548644
     server_dur: 20604
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28525,11 +30069,13 @@
     client_ts: 24932907687
     client_dur: 61496
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24932929991
     server_dur: 21719
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28562,11 +30108,13 @@
     client_ts: 24936245311
     client_dur: 89729
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24936266250
     server_dur: 20798
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28593,11 +30141,13 @@
     client_ts: 24943311799
     client_dur: 214359
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24943331082
     server_dur: 20072
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28624,11 +30174,13 @@
     client_ts: 24967426046
     client_dur: 238772
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 24967447878
     server_dur: 21398
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28655,11 +30207,13 @@
     client_ts: 25003883218
     client_dur: 324891
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25003925054
     server_dur: 43609
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28686,11 +30240,13 @@
     client_ts: 25025076994
     client_dur: 190930
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25025118986
     server_dur: 30435
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28723,11 +30279,13 @@
     client_ts: 25034377812
     client_dur: 271653
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25034403338
     server_dur: 27240
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28754,11 +30312,13 @@
     client_ts: 25036622369
     client_dur: 268217
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25036650886
     server_dur: 26015
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28785,11 +30345,13 @@
     client_ts: 25048460597
     client_dur: 56503
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25048480970
     server_dur: 20752
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28816,11 +30378,13 @@
     client_ts: 25056764479
     client_dur: 56793
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25056784331
     server_dur: 20103
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28847,11 +30411,13 @@
     client_ts: 25073721612
     client_dur: 55061
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25073740814
     server_dur: 20447
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28878,11 +30444,13 @@
     client_ts: 25110161805
     client_dur: 81154
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25110196375
     server_dur: 24505
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28915,11 +30483,13 @@
     client_ts: 25118254173
     client_dur: 89345
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25118275515
     server_dur: 30281
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28946,11 +30516,13 @@
     client_ts: 25126211905
     client_dur: 156793
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25126252664
     server_dur: 31152
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -28983,11 +30555,13 @@
     client_ts: 25128591519
     client_dur: 86261
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25128613762
     server_dur: 22079
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29014,11 +30588,13 @@
     client_ts: 25137455943
     client_dur: 138560
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25137492801
     server_dur: 24108
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29051,11 +30627,13 @@
     client_ts: 25171098007
     client_dur: 86786
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25171119721
     server_dur: 24637
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29082,11 +30660,13 @@
     client_ts: 25176666011
     client_dur: 68313
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25176690933
     server_dur: 22908
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29125,11 +30705,13 @@
     client_ts: 25180015030
     client_dur: 119578
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25180094139
     server_dur: 21082
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29168,11 +30750,13 @@
     client_ts: 25185495297
     client_dur: 90240
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25185546375
     server_dur: 20903
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29211,11 +30795,13 @@
     client_ts: 25190169442
     client_dur: 56523
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25190190035
     server_dur: 19360
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29248,11 +30834,13 @@
     client_ts: 25192159947
     client_dur: 67609
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25192191721
     server_dur: 19584
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29285,11 +30873,13 @@
     client_ts: 25194833721
     client_dur: 54386
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25194853231
     server_dur: 19541
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29316,11 +30906,13 @@
     client_ts: 25197865886
     client_dur: 55290
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25197885904
     server_dur: 19555
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29347,11 +30939,13 @@
     client_ts: 25205510080
     client_dur: 53971
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25205529822
     server_dur: 19108
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29378,11 +30972,13 @@
     client_ts: 25210682525
     client_dur: 54427
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25210701854
     server_dur: 19875
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29409,11 +31005,13 @@
     client_ts: 25212773102
     client_dur: 143598
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25212807375
     server_dur: 20140
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29446,11 +31044,13 @@
     client_ts: 25219095678
     client_dur: 62570
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25219117631
     server_dur: 20590
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29490,11 +31090,13 @@
     client_ts: 25230101202
     client_dur: 295436
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25230125660
     server_dur: 202423
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29528,11 +31130,13 @@
     client_ts: 25243511980
     client_dur: 489650
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25243544499
     server_dur: 438512
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -29590,11 +31194,13 @@
     client_ts: 25244949065
     client_dur: 33302645
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25244971300
     server_dur: 33241468
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -29676,11 +31282,13 @@
     client_ts: 25279371214
     client_dur: 141670
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25279387389
     server_dur: 110471
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29714,11 +31322,13 @@
     client_ts: 25279567724
     client_dur: 1117204
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25279592927
     server_dur: 1062729
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -29776,11 +31386,13 @@
     client_ts: 25280736368
     client_dur: 173449
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25280756522
     server_dur: 131586
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29814,11 +31426,13 @@
     client_ts: 25280932813
     client_dur: 166964
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25280946041
     server_dur: 122533
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29852,11 +31466,13 @@
     client_ts: 25281131360
     client_dur: 127300
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25281145719
     server_dur: 98609
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29890,11 +31506,13 @@
     client_ts: 25281273755
     client_dur: 152610
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25281315273
     server_dur: 97815
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29928,11 +31546,13 @@
     client_ts: 25281454812
     client_dur: 120876
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25281470206
     server_dur: 94381
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -29966,11 +31586,13 @@
     client_ts: 25281590129
     client_dur: 151723
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25281611020
     server_dur: 119089
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30004,11 +31626,13 @@
     client_ts: 25281756115
     client_dur: 115379
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25281769371
     server_dur: 91666
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30042,11 +31666,13 @@
     client_ts: 25281884499
     client_dur: 116250
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25281896268
     server_dur: 93727
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30080,11 +31706,13 @@
     client_ts: 25282021405
     client_dur: 113709
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25282032972
     server_dur: 91541
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30118,11 +31746,13 @@
     client_ts: 25282147043
     client_dur: 114363
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25282159024
     server_dur: 91525
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30156,11 +31786,13 @@
     client_ts: 25282273296
     client_dur: 113496
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25282285050
     server_dur: 91191
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30194,11 +31826,13 @@
     client_ts: 25282398433
     client_dur: 133314
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25282413336
     server_dur: 107722
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30232,11 +31866,13 @@
     client_ts: 25282543082
     client_dur: 113069
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25282556151
     server_dur: 89003
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30270,11 +31906,13 @@
     client_ts: 25282667987
     client_dur: 110166
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25282679437
     server_dur: 87774
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30308,11 +31946,13 @@
     client_ts: 25282789771
     client_dur: 113837
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25282802242
     server_dur: 90943
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30346,11 +31986,13 @@
     client_ts: 25282914877
     client_dur: 111627
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25282927602
     server_dur: 88554
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30384,11 +32026,13 @@
     client_ts: 25283038152
     client_dur: 1992379
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_2"
     server_ts: 25284866843
     server_dur: 141149
     server_tid: 548
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30421,11 +32065,13 @@
     client_ts: 25374394471
     client_dur: 2049689
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25374452290
     server_dur: 56976
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30452,11 +32098,13 @@
     client_ts: 25376513735
     client_dur: 1298945
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25376552511
     server_dur: 1229818
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -30513,11 +32161,13 @@
     client_ts: 25380873939
     client_dur: 94827
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25380918552
     server_dur: 29189
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30544,11 +32194,13 @@
     client_ts: 25382555538
     client_dur: 499495
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25382942898
     server_dur: 73464
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30581,11 +32233,13 @@
     client_ts: 25383224119
     client_dur: 475348
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25383318022
     server_dur: 71453
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30618,11 +32272,13 @@
     client_ts: 25384246889
     client_dur: 2183818
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25386357038
     server_dur: 45022
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30655,11 +32311,13 @@
     client_ts: 25386462748
     client_dur: 100360
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25386487821
     server_dur: 60382
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30686,11 +32344,13 @@
     client_ts: 25386597595
     client_dur: 98608
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25386619074
     server_dur: 61591
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30717,11 +32377,13 @@
     client_ts: 25386719729
     client_dur: 86786
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25386741757
     server_dur: 49965
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30748,11 +32410,13 @@
     client_ts: 25386829528
     client_dur: 89755
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25386849083
     server_dur: 55739
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30779,11 +32443,13 @@
     client_ts: 25386948016
     client_dur: 83210
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25386968208
     server_dur: 48426
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30810,11 +32476,13 @@
     client_ts: 25387051427
     client_dur: 82153
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25387070661
     server_dur: 48379
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30841,11 +32509,13 @@
     client_ts: 25387162195
     client_dur: 82295
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25387182559
     server_dur: 47439
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30872,11 +32542,13 @@
     client_ts: 25387268275
     client_dur: 86930
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25387287442
     server_dur: 52733
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30903,11 +32575,13 @@
     client_ts: 25400532103
     client_dur: 58804
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25400550889
     server_dur: 23976
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30934,11 +32608,13 @@
     client_ts: 25400815946
     client_dur: 53189
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25400833837
     server_dur: 20666
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30965,11 +32641,13 @@
     client_ts: 25426768392
     client_dur: 59432
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25426787956
     server_dur: 24158
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -30996,11 +32674,13 @@
     client_ts: 25428341210
     client_dur: 87943
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25428360065
     server_dur: 51321
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31028,11 +32708,13 @@
     client_ts: 25428597840
     client_dur: 1897216
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25428614184
     server_dur: 1721051
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -31108,11 +32790,13 @@
     client_ts: 25430545529
     client_dur: 5604165
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25430555701
     server_dur: 5580114
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -31176,11 +32860,13 @@
     client_ts: 25436197115
     client_dur: 165295
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_2"
     server_ts: 25436268304
     server_dur: 84720
     server_tid: 541
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31214,11 +32900,13 @@
     client_ts: 25436454693
     client_dur: 101710
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25436467750
     server_dur: 68620
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -31258,11 +32946,13 @@
     client_ts: 25436579821
     client_dur: 1131075
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_2"
     server_ts: 25436605605
     server_dur: 1092472
     server_tid: 541
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31296,11 +32986,13 @@
     client_ts: 25437807385
     client_dur: 35309
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_2"
     server_ts: 25437821151
     server_dur: 13284
     server_tid: 541
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31334,11 +33026,13 @@
     client_ts: 25437906939
     client_dur: 108314
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25437930754
     server_dur: 65111
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -31384,11 +33078,13 @@
     client_ts: 25438037598
     client_dur: 1231074
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25438067211
     server_dur: 1188167
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -31428,11 +33124,13 @@
     client_ts: 25439303208
     client_dur: 56463
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25439340083
     server_dur: 11933
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31466,11 +33164,13 @@
     client_ts: 25439404915
     client_dur: 28249
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25439417069
     server_dur: 8812
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31504,11 +33204,13 @@
     client_ts: 25439834153
     client_dur: 149238
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25439854310
     server_dur: 63659
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -31548,11 +33250,13 @@
     client_ts: 25440007897
     client_dur: 617701
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25440026034
     server_dur: 581007
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31586,11 +33290,13 @@
     client_ts: 25440650480
     client_dur: 37954
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25440669130
     server_dur: 11644
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31624,11 +33330,13 @@
     client_ts: 25440744629
     client_dur: 130786
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25440759041
     server_dur: 97590
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -31668,11 +33376,13 @@
     client_ts: 25440898977
     client_dur: 1131318
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25440930592
     server_dur: 1086590
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -31718,11 +33428,13 @@
     client_ts: 25442065948
     client_dur: 37271
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_1"
     server_ts: 25442083599
     server_dur: 12197
     server_tid: 557
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31756,11 +33468,13 @@
     client_ts: 25442154751
     client_dur: 91298
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25442164438
     server_dur: 64020
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -31800,11 +33514,13 @@
     client_ts: 25442267983
     client_dur: 969844
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25442291581
     server_dur: 932552
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -31844,11 +33560,13 @@
     client_ts: 25443270028
     client_dur: 50947
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25443301118
     server_dur: 11975
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -31882,11 +33600,13 @@
     client_ts: 25443369306
     client_dur: 97383
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25443387190
     server_dur: 61382
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -31926,11 +33646,13 @@
     client_ts: 25443488838
     client_dur: 710384
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25443518308
     server_dur: 668611
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -31976,11 +33698,13 @@
     client_ts: 25444231572
     client_dur: 52027
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25444264016
     server_dur: 12085
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32014,11 +33738,13 @@
     client_ts: 25444518761
     client_dur: 104456
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25444539803
     server_dur: 63755
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32058,11 +33784,13 @@
     client_ts: 25444646123
     client_dur: 413789
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25444674530
     server_dur: 371297
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32096,11 +33824,13 @@
     client_ts: 25445078459
     client_dur: 37933
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25445097539
     server_dur: 11521
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32134,11 +33864,13 @@
     client_ts: 25445162965
     client_dur: 98821
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25445176700
     server_dur: 65755
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32178,11 +33910,13 @@
     client_ts: 25445283998
     client_dur: 1223250
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25445325570
     server_dur: 1169074
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32228,11 +33962,13 @@
     client_ts: 25446539928
     client_dur: 55845
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25446574718
     server_dur: 12523
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32266,11 +34002,13 @@
     client_ts: 25446655842
     client_dur: 102770
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25446673983
     server_dur: 64748
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32310,11 +34048,13 @@
     client_ts: 25446780771
     client_dur: 484620
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25446802921
     server_dur: 383705
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32354,11 +34094,13 @@
     client_ts: 25447296204
     client_dur: 32106
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25447307403
     server_dur: 12025
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32392,11 +34134,13 @@
     client_ts: 25447383428
     client_dur: 99654
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25447399103
     server_dur: 64249
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32436,11 +34180,13 @@
     client_ts: 25447504358
     client_dur: 1088603
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25447527457
     server_dur: 1052788
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -32486,11 +34232,13 @@
     client_ts: 25448618882
     client_dur: 65160
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25448662606
     server_dur: 12297
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32524,11 +34272,13 @@
     client_ts: 25448732540
     client_dur: 90622
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25448745055
     server_dur: 61605
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32568,11 +34318,13 @@
     client_ts: 25448844089
     client_dur: 1341222
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25448878397
     server_dur: 1294973
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32612,11 +34364,13 @@
     client_ts: 25450214696
     client_dur: 132324
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25450325232
     server_dur: 12314
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32650,11 +34404,13 @@
     client_ts: 25450401044
     client_dur: 34066
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25450413421
     server_dur: 8832
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32688,11 +34444,13 @@
     client_ts: 25450477847
     client_dur: 108090
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25450488460
     server_dur: 82190
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32732,11 +34490,13 @@
     client_ts: 25450607703
     client_dur: 1259215
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25450647353
     server_dur: 1203353
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32776,11 +34536,13 @@
     client_ts: 25451902370
     client_dur: 111453
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25451993380
     server_dur: 11814
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32814,11 +34576,13 @@
     client_ts: 25452994340
     client_dur: 109726
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25453017159
     server_dur: 64867
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32858,11 +34622,13 @@
     client_ts: 25453126725
     client_dur: 1046699
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25453215693
     server_dur: 328275
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32896,11 +34662,13 @@
     client_ts: 25454206920
     client_dur: 37100
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25454216327
     server_dur: 14099
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -32934,11 +34702,13 @@
     client_ts: 25454305892
     client_dur: 105614
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25454321058
     server_dur: 71322
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -32978,11 +34748,13 @@
     client_ts: 25454433615
     client_dur: 1182599
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25454454326
     server_dur: 1148464
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -33022,11 +34794,13 @@
     client_ts: 25455648152
     client_dur: 89055
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25455717688
     server_dur: 10552
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33060,11 +34834,13 @@
     client_ts: 25455783097
     client_dur: 77504
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25455796471
     server_dur: 53966
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33098,11 +34874,13 @@
     client_ts: 25455870323
     client_dur: 183133
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25455880236
     server_dur: 164382
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33136,11 +34914,13 @@
     client_ts: 25456454160
     client_dur: 31893
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25456468473
     server_dur: 7849
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33174,11 +34954,13 @@
     client_ts: 25456548523
     client_dur: 98167
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25456560742
     server_dur: 75793
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -33218,11 +35000,13 @@
     client_ts: 25456659359
     client_dur: 202533
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25456689304
     server_dur: 163129
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33256,11 +35040,13 @@
     client_ts: 25456872988
     client_dur: 29135
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/idmap2d"
     server_thread: "binder:541_3"
     server_ts: 25456888657
     server_dur: 6402
     server_tid: 1598
+    server_pid: 541
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33293,11 +35079,13 @@
     client_ts: 25494548191
     client_dur: 258095
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25494622231
     server_dur: 150527
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33330,11 +35118,13 @@
     client_ts: 25495244473
     client_dur: 325048
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25495400405
     server_dur: 128258
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33367,11 +35157,13 @@
     client_ts: 25504141418
     client_dur: 297361
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25504207270
     server_dur: 133515
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33398,11 +35190,13 @@
     client_ts: 25511329109
     client_dur: 678596
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25511805543
     server_dur: 136659
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33435,11 +35229,13 @@
     client_ts: 25517948532
     client_dur: 163240
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25518009843
     server_dur: 44297
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33472,11 +35268,13 @@
     client_ts: 25518739183
     client_dur: 3265864
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25518779467
     server_dur: 374561
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33509,11 +35307,13 @@
     client_ts: 25523067296
     client_dur: 1153803
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25523345752
     server_dur: 24699
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33546,11 +35346,13 @@
     client_ts: 25529407157
     client_dur: 1243348
     client_tid: 641
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.health-service.cuttlefish"
     server_thread: "android.hardwar"
     server_ts: 25529886580
     server_dur: 502254
     server_tid: 431
+    server_pid: 431
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -33589,11 +35391,13 @@
     client_ts: 25530820205
     client_dur: 1517480
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25530877699
     server_dur: 79639
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33626,11 +35430,13 @@
     client_ts: 25538312793
     client_dur: 177283
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25538360355
     server_dur: 73011
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33663,11 +35469,13 @@
     client_ts: 25538521832
     client_dur: 115418
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25538557132
     server_dur: 51687
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33700,11 +35508,13 @@
     client_ts: 25555649759
     client_dur: 401986
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25555822535
     server_dur: 166864
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33737,11 +35547,13 @@
     client_ts: 25562439114
     client_dur: 360669
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25562601598
     server_dur: 147343
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33774,11 +35586,13 @@
     client_ts: 25564138751
     client_dur: 227016
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25564207677
     server_dur: 112964
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33811,11 +35625,13 @@
     client_ts: 25564950504
     client_dur: 216354
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25565018623
     server_dur: 104733
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33848,11 +35664,13 @@
     client_ts: 25565861693
     client_dur: 154693
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25565923809
     server_dur: 44929
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33886,11 +35704,13 @@
     client_ts: 25566098278
     client_dur: 3268381
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25566134660
     server_dur: 2297021
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33923,11 +35743,13 @@
     client_ts: 25578643057
     client_dur: 230121
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25578709142
     server_dur: 126093
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33954,11 +35776,13 @@
     client_ts: 25579716604
     client_dur: 216367
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25579776806
     server_dur: 121078
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -33985,11 +35809,13 @@
     client_ts: 25584224256
     client_dur: 249444
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25584297755
     server_dur: 135137
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34016,11 +35842,13 @@
     client_ts: 25585086651
     client_dur: 284206
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25585152822
     server_dur: 129855
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34047,11 +35875,13 @@
     client_ts: 25587741520
     client_dur: 249445
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25587810843
     server_dur: 138515
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34078,11 +35908,13 @@
     client_ts: 25588452300
     client_dur: 243841
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25588512250
     server_dur: 145542
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34109,11 +35941,13 @@
     client_ts: 25607046968
     client_dur: 141541
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25607085845
     server_dur: 83802
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34140,11 +35974,13 @@
     client_ts: 25626771532
     client_dur: 407539
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25627067466
     server_dur: 61702
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34177,11 +36013,13 @@
     client_ts: 25627247935
     client_dur: 118074
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25627290693
     server_dur: 38186
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34208,11 +36046,13 @@
     client_ts: 25627461473
     client_dur: 244900
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25627519338
     server_dur: 149891
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34239,11 +36079,13 @@
     client_ts: 25632313567
     client_dur: 258248
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25632388552
     server_dur: 141000
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34270,11 +36112,13 @@
     client_ts: 25637260298
     client_dur: 196517
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25637328529
     server_dur: 88341
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34301,11 +36145,13 @@
     client_ts: 25656975183
     client_dur: 72577
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25656999460
     server_dur: 31187
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34332,11 +36178,13 @@
     client_ts: 25657158859
     client_dur: 49143
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25657175302
     server_dur: 18337
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34363,11 +36211,13 @@
     client_ts: 25659444951
     client_dur: 67480
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25659467572
     server_dur: 27682
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34394,11 +36244,13 @@
     client_ts: 25710903029
     client_dur: 196994
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25710975935
     server_dur: 69286
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34431,11 +36283,13 @@
     client_ts: 25779384485
     client_dur: 6404465
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25779465833
     server_dur: 115210
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34468,11 +36322,13 @@
     client_ts: 25785903212
     client_dur: 173175
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25785969981
     server_dur: 74609
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34505,11 +36361,13 @@
     client_ts: 25786187023
     client_dur: 71969
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25786216656
     server_dur: 16443
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34542,11 +36400,13 @@
     client_ts: 25788338381
     client_dur: 66978
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25788365130
     server_dur: 26182
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34579,11 +36439,13 @@
     client_ts: 25788426430
     client_dur: 44938
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25788444575
     server_dur: 12371
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34616,11 +36478,13 @@
     client_ts: 25794664939
     client_dur: 125489
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25794702859
     server_dur: 70821
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34647,11 +36511,13 @@
     client_ts: 25803399428
     client_dur: 125121
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25803446233
     server_dur: 61602
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34684,11 +36550,13 @@
     client_ts: 25805561097
     client_dur: 258904
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25805581639
     server_dur: 24195
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34715,11 +36583,13 @@
     client_ts: 25806638177
     client_dur: 383567
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25806656214
     server_dur: 348438
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34746,11 +36616,13 @@
     client_ts: 25807042562
     client_dur: 45260
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25807056567
     server_dur: 14991
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34777,11 +36649,13 @@
     client_ts: 25807129635
     client_dur: 626529
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25807318058
     server_dur: 335442
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "D"
@@ -34832,11 +36706,13 @@
     client_ts: 25807794682
     client_dur: 197460
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25807808849
     server_dur: 31598
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34869,11 +36745,13 @@
     client_ts: 25808041191
     client_dur: 152874
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808112547
     server_dur: 38477
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34906,11 +36784,13 @@
     client_ts: 25808300543
     client_dur: 216110
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808488934
     server_dur: 5816
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34943,11 +36823,13 @@
     client_ts: 25808536171
     client_dur: 29720
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808549475
     server_dur: 6142
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -34980,11 +36862,13 @@
     client_ts: 25808580143
     client_dur: 23193
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808589636
     server_dur: 4653
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35017,11 +36901,13 @@
     client_ts: 25808614611
     client_dur: 24483
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808626104
     server_dur: 4244
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35054,11 +36940,13 @@
     client_ts: 25808649459
     client_dur: 19535
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808656163
     server_dur: 4039
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35091,11 +36979,13 @@
     client_ts: 25808679113
     client_dur: 22778
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808688986
     server_dur: 3889
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35128,11 +37018,13 @@
     client_ts: 25808712146
     client_dur: 21102
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808720606
     server_dur: 3664
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35165,11 +37057,13 @@
     client_ts: 25808743343
     client_dur: 49190
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808773138
     server_dur: 4014
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35202,11 +37096,13 @@
     client_ts: 25808803408
     client_dur: 47244
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808832840
     server_dur: 3744
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35239,11 +37135,13 @@
     client_ts: 25808861280
     client_dur: 23141
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808871693
     server_dur: 4083
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35276,11 +37174,13 @@
     client_ts: 25808894304
     client_dur: 22212
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808903846
     server_dur: 4193
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35313,11 +37213,13 @@
     client_ts: 25808927915
     client_dur: 23489
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808938201
     server_dur: 4441
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35350,11 +37252,13 @@
     client_ts: 25808963769
     client_dur: 21106
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25808972274
     server_dur: 4301
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35387,11 +37291,13 @@
     client_ts: 25808996827
     client_dur: 22150
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809006677
     server_dur: 3852
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35424,11 +37330,13 @@
     client_ts: 25809029487
     client_dur: 30653
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809048418
     server_dur: 3146
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35461,11 +37369,13 @@
     client_ts: 25809070927
     client_dur: 105314
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809156958
     server_dur: 4090
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35498,11 +37408,13 @@
     client_ts: 25809187458
     client_dur: 21199
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809196575
     server_dur: 3729
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35535,11 +37447,13 @@
     client_ts: 25809219112
     client_dur: 20851
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809227683
     server_dur: 3856
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35572,11 +37486,13 @@
     client_ts: 25809250949
     client_dur: 271118
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809502291
     server_dur: 4522
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35609,11 +37525,13 @@
     client_ts: 25809543172
     client_dur: 29191
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809555589
     server_dur: 4714
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35646,11 +37564,13 @@
     client_ts: 25809583743
     client_dur: 23569
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809592470
     server_dur: 3948
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35683,11 +37603,13 @@
     client_ts: 25809618388
     client_dur: 38876
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809637992
     server_dur: 8798
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35720,11 +37642,13 @@
     client_ts: 25809757746
     client_dur: 171172
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809770125
     server_dur: 89637
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -35763,11 +37687,13 @@
     client_ts: 25809948987
     client_dur: 120274
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25809967298
     server_dur: 27536
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35800,11 +37726,13 @@
     client_ts: 25810106019
     client_dur: 110073
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25810132739
     server_dur: 5193
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35837,11 +37765,13 @@
     client_ts: 25810239347
     client_dur: 148792
     client_tid: 641
+    client_pid: 641
     server_process: "/apex/com.android.hardware.vibrator/bin/hw/android.hardware.vibrator-service.example"
     server_thread: "android.hardwar"
     server_ts: 25810364813
     server_dur: 3865
     server_tid: 481
+    server_pid: 481
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35874,11 +37804,13 @@
     client_ts: 25810497904
     client_dur: 126359
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25810531969
     server_dur: 73542
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35905,11 +37837,13 @@
     client_ts: 25810646589
     client_dur: 91503
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25810669576
     server_dur: 52662
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35936,11 +37870,13 @@
     client_ts: 25810948647
     client_dur: 113503
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25810980358
     server_dur: 64314
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35967,11 +37903,13 @@
     client_ts: 25811246975
     client_dur: 366342
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25811269261
     server_dur: 324989
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -35998,11 +37936,13 @@
     client_ts: 25811641918
     client_dur: 83600
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25811659549
     server_dur: 47010
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -36035,11 +37975,13 @@
     client_ts: 25811827364
     client_dur: 105911
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25811855742
     server_dur: 60946
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36066,11 +38008,13 @@
     client_ts: 25814468030
     client_dur: 128573
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25814502303
     server_dur: 75807
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36097,11 +38041,13 @@
     client_ts: 25824457100
     client_dur: 243653
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25824484172
     server_dur: 202360
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -36134,11 +38080,13 @@
     client_ts: 25826929585
     client_dur: 134380
     client_tid: 1618
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25826953314
     server_dur: 57893
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36171,11 +38119,13 @@
     client_ts: 25827095345
     client_dur: 59551
     client_tid: 1618
+    client_pid: 641
     server_process: "/apex/com.android.os.statsd/bin/statsd"
     server_thread: "binder:415_3"
     server_ts: 25827112546
     server_dur: 28045
     server_tid: 422
+    server_pid: 415
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36208,11 +38158,13 @@
     client_ts: 25828178041
     client_dur: 75912
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25828192051
     server_dur: 26713
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36245,11 +38197,13 @@
     client_ts: 25828289955
     client_dur: 48095
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25828303964
     server_dur: 23818
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36282,11 +38236,13 @@
     client_ts: 25833585425
     client_dur: 213739
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25833674904
     server_dur: 59579
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36319,11 +38275,13 @@
     client_ts: 25834911169
     client_dur: 66864
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25834940005
     server_dur: 22905
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36356,11 +38314,13 @@
     client_ts: 25835009103
     client_dur: 41035
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25835029405
     server_dur: 9917
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36393,11 +38353,13 @@
     client_ts: 25835084036
     client_dur: 39828
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25835102285
     server_dur: 11104
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36430,11 +38392,13 @@
     client_ts: 25835225817
     client_dur: 39668
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25835244300
     server_dur: 10401
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36467,11 +38431,13 @@
     client_ts: 25835491757
     client_dur: 94706
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25835523199
     server_dur: 50044
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36504,11 +38470,13 @@
     client_ts: 25838116144
     client_dur: 101371
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25838145038
     server_dur: 57786
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36535,11 +38503,13 @@
     client_ts: 25854689410
     client_dur: 72379
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25854716713
     server_dur: 29735
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36572,11 +38542,13 @@
     client_ts: 25854776152
     client_dur: 84994
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25854792697
     server_dur: 55053
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36609,11 +38581,13 @@
     client_ts: 25856165265
     client_dur: 41372
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25856179451
     server_dur: 11065
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36646,11 +38620,13 @@
     client_ts: 25859386674
     client_dur: 62804
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25859406671
     server_dur: 23198
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36677,11 +38653,13 @@
     client_ts: 25860119896
     client_dur: 89982
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25860140536
     server_dur: 54911
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36708,11 +38686,13 @@
     client_ts: 25861940989
     client_dur: 112198
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25861960190
     server_dur: 69698
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36745,11 +38725,13 @@
     client_ts: 25862411224
     client_dur: 72918
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25862426577
     server_dur: 43364
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36782,11 +38764,13 @@
     client_ts: 25862572364
     client_dur: 43335
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25862583679
     server_dur: 21214
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36819,11 +38803,13 @@
     client_ts: 25862644016
     client_dur: 35084
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25862653442
     server_dur: 16522
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36856,11 +38842,13 @@
     client_ts: 25862703240
     client_dur: 43193
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25862712076
     server_dur: 25328
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36893,11 +38881,13 @@
     client_ts: 25863309305
     client_dur: 56073
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25863321886
     server_dur: 30965
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36930,11 +38920,13 @@
     client_ts: 25863414967
     client_dur: 48999
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25863426312
     server_dur: 27403
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -36967,11 +38959,13 @@
     client_ts: 25864046858
     client_dur: 61808
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864062070
     server_dur: 32956
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37004,11 +38998,13 @@
     client_ts: 25864143436
     client_dur: 51337
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864153386
     server_dur: 30980
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37041,11 +39037,13 @@
     client_ts: 25864216828
     client_dur: 37990
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864226073
     server_dur: 19004
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37078,11 +39076,13 @@
     client_ts: 25864279759
     client_dur: 37961
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864288819
     server_dur: 19366
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37115,11 +39115,13 @@
     client_ts: 25864345023
     client_dur: 59115
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864354391
     server_dur: 17778
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37152,11 +39154,13 @@
     client_ts: 25864449726
     client_dur: 51629
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864460717
     server_dur: 26406
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37189,11 +39193,13 @@
     client_ts: 25864530175
     client_dur: 40230
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864539824
     server_dur: 19580
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37226,11 +39232,13 @@
     client_ts: 25864603443
     client_dur: 64958
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864612742
     server_dur: 44603
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37263,11 +39271,13 @@
     client_ts: 25864687472
     client_dur: 38679
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864696221
     server_dur: 19520
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37300,11 +39310,13 @@
     client_ts: 25864748376
     client_dur: 41144
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864757238
     server_dur: 21548
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37337,11 +39349,13 @@
     client_ts: 25864814870
     client_dur: 37926
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864824299
     server_dur: 17299
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37374,11 +39388,13 @@
     client_ts: 25864878443
     client_dur: 41432
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864887850
     server_dur: 21263
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37411,11 +39427,13 @@
     client_ts: 25864943354
     client_dur: 46446
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25864953070
     server_dur: 26259
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37448,11 +39466,13 @@
     client_ts: 25865010220
     client_dur: 42768
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25865018943
     server_dur: 22857
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37485,11 +39505,13 @@
     client_ts: 25865078882
     client_dur: 42845
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25865088374
     server_dur: 22790
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37522,11 +39544,13 @@
     client_ts: 25865143746
     client_dur: 43541
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25865152587
     server_dur: 24329
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37559,11 +39583,13 @@
     client_ts: 25865208871
     client_dur: 37989
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25865217954
     server_dur: 18361
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37596,11 +39622,13 @@
     client_ts: 25865269510
     client_dur: 180816
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25865278803
     server_dur: 44675
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -37639,11 +39667,13 @@
     client_ts: 25865509380
     client_dur: 1204393
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25866655580
     server_dur: 37015
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37676,11 +39706,13 @@
     client_ts: 25866765533
     client_dur: 52077
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25866778140
     server_dur: 25314
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37713,11 +39745,13 @@
     client_ts: 25866847306
     client_dur: 36125
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25866856611
     server_dur: 17873
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37750,11 +39784,13 @@
     client_ts: 25866907704
     client_dur: 31809
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25866916674
     server_dur: 14121
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37787,11 +39823,13 @@
     client_ts: 25866963934
     client_dur: 42576
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25866972903
     server_dur: 18950
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37824,11 +39862,13 @@
     client_ts: 25867031282
     client_dur: 35871
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867040421
     server_dur: 17984
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37861,11 +39901,13 @@
     client_ts: 25867092641
     client_dur: 34611
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867101335
     server_dur: 17329
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37898,11 +39940,13 @@
     client_ts: 25867155069
     client_dur: 43809
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867163773
     server_dur: 26243
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37935,11 +39979,13 @@
     client_ts: 25867229729
     client_dur: 32631
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867238857
     server_dur: 14033
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -37972,11 +40018,13 @@
     client_ts: 25867283252
     client_dur: 40731
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867292366
     server_dur: 21993
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38009,11 +40057,13 @@
     client_ts: 25867343753
     client_dur: 34724
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867353084
     server_dur: 16650
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38046,11 +40096,13 @@
     client_ts: 25867401523
     client_dur: 50745
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867410223
     server_dur: 32741
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38083,11 +40135,13 @@
     client_ts: 25867475251
     client_dur: 46815
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867485001
     server_dur: 26611
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38120,11 +40174,13 @@
     client_ts: 25867547843
     client_dur: 36696
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867557467
     server_dur: 18125
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38157,11 +40213,13 @@
     client_ts: 25867605981
     client_dur: 37415
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867614819
     server_dur: 19663
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38194,11 +40252,13 @@
     client_ts: 25867663292
     client_dur: 35879
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867672122
     server_dur: 17373
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38231,11 +40291,13 @@
     client_ts: 25867751966
     client_dur: 40848
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867761875
     server_dur: 19072
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38269,11 +40331,13 @@
     client_ts: 25867845030
     client_dur: 180504
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25867860282
     server_dur: 154468
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R"
@@ -38318,11 +40382,13 @@
     client_ts: 25872260669
     client_dur: 83399
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25872278450
     server_dur: 47096
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38355,11 +40421,13 @@
     client_ts: 25885439966
     client_dur: 1549817
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25886054638
     server_dur: 24145
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38392,11 +40460,13 @@
     client_ts: 25885831452
     client_dur: 1452935
     client_tid: 1623
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25886206332
     server_dur: 305512
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38429,11 +40499,13 @@
     client_ts: 25887309449
     client_dur: 115655
     client_tid: 1623
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25887326271
     server_dur: 18512
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38460,11 +40532,13 @@
     client_ts: 25887452954
     client_dur: 283867
     client_tid: 1623
+    client_pid: 641
     server_process: "/vendor/bin/hw/android.hardware.input.processor-service.example"
     server_thread: "android.hardwar"
     server_ts: 25887467255
     server_dur: 38055
     server_tid: 447
+    server_pid: 447
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38491,11 +40565,13 @@
     client_ts: 25892252212
     client_dur: 2274293
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25894481596
     server_dur: 27702
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38528,11 +40604,13 @@
     client_ts: 25921269728
     client_dur: 400263
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25921599983
     server_dur: 52448
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38565,11 +40643,13 @@
     client_ts: 25921747025
     client_dur: 209278
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25921878602
     server_dur: 66173
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38602,11 +40682,13 @@
     client_ts: 25922308759
     client_dur: 230930
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25922366800
     server_dur: 58914
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38639,11 +40721,13 @@
     client_ts: 25926152660
     client_dur: 106426
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25926184604
     server_dur: 59798
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38676,11 +40760,13 @@
     client_ts: 25935520343
     client_dur: 106898
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25935553945
     server_dur: 59988
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38713,11 +40799,13 @@
     client_ts: 25936096478
     client_dur: 38431
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25936114070
     server_dur: 13878
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38750,11 +40838,13 @@
     client_ts: 25936154115
     client_dur: 304852
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25936427202
     server_dur: 13388
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38787,11 +40877,13 @@
     client_ts: 25939273894
     client_dur: 44659
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25939290557
     server_dur: 19990
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38824,11 +40916,13 @@
     client_ts: 25939575552
     client_dur: 81712
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25939602201
     server_dur: 44933
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38861,11 +40955,13 @@
     client_ts: 25944988176
     client_dur: 71994
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25945018540
     server_dur: 25217
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38898,11 +40994,13 @@
     client_ts: 25948938076
     client_dur: 82091
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25948980882
     server_dur: 27091
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38935,11 +41033,13 @@
     client_ts: 25951851055
     client_dur: 445134
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_3"
     server_ts: 25951902501
     server_dur: 183967
     server_tid: 1575
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -38972,11 +41072,13 @@
     client_ts: 25952609949
     client_dur: 44997
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_2"
     server_ts: 25952623297
     server_dur: 18258
     server_tid: 522
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39003,11 +41105,13 @@
     client_ts: 25954386897
     client_dur: 107947
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/surfaceflinger"
     server_thread: "binder:496_3"
     server_ts: 25954404617
     server_dur: 18018
     server_tid: 1575
+    server_pid: 496
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39040,11 +41144,13 @@
     client_ts: 25955417145
     client_dur: 248980
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25955442657
     server_dur: 23870
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39078,11 +41184,13 @@
     client_ts: 25955702503
     client_dur: 1028567
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25955716400
     server_dur: 923564
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39109,11 +41217,13 @@
     client_ts: 25957071212
     client_dur: 244964
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25957109467
     server_dur: 68828
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39146,11 +41256,13 @@
     client_ts: 25957345364
     client_dur: 86035
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25957361868
     server_dur: 39522
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39183,11 +41295,13 @@
     client_ts: 25957477722
     client_dur: 38342
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25957490997
     server_dur: 14607
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39220,11 +41334,13 @@
     client_ts: 25957561902
     client_dur: 115285
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/vold"
     server_thread: "binder:255_2"
     server_ts: 25957584457
     server_dur: 69879
     server_tid: 255
+    server_pid: 255
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39257,11 +41373,13 @@
     client_ts: 25957701552
     client_dur: 31673
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/vold"
     server_thread: "binder:255_2"
     server_ts: 25957712540
     server_dur: 11337
     server_tid: 255
+    server_pid: 255
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39294,11 +41412,13 @@
     client_ts: 25957917714
     client_dur: 81359
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25957965656
     server_dur: 19953
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39331,11 +41451,13 @@
     client_ts: 25958179475
     client_dur: 57952
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25958197817
     server_dur: 12909
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39368,11 +41490,13 @@
     client_ts: 25958259037
     client_dur: 37816
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25958272198
     server_dur: 12201
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39406,11 +41530,13 @@
     client_ts: 25958323889
     client_dur: 934386
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25958335597
     server_dur: 816593
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39438,11 +41564,13 @@
     client_ts: 25959279820
     client_dur: 780373
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/installd"
     server_thread: "binder:548_1"
     server_ts: 25959289960
     server_dur: 697719
     server_tid: 565
+    server_pid: 548
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "R+"
@@ -39475,11 +41603,13 @@
     client_ts: 25960212392
     client_dur: 138312
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25960246748
     server_dur: 59922
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39512,11 +41642,13 @@
     client_ts: 25960703658
     client_dur: 185272
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25960733334
     server_dur: 55452
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
@@ -39549,11 +41681,13 @@
     client_ts: 25964272455
     client_dur: 108953
     client_tid: 641
+    client_pid: 641
     server_process: "/system/bin/servicemanager"
     server_thread: "servicemanager"
     server_ts: 25964304630
     server_dur: 66130
     server_tid: 243
+    server_pid: 243
     thread_states {
       thread_state_type: "binder_reply"
       thread_state: "Running"
diff --git a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out b/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out
index aa63194..d75d44d 100644
--- a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out
+++ b/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.out
@@ -5,6 +5,7 @@
     process {
       name: "com.android.systemui"
       uid: 10001
+      pid: 1000
     }
     ts: 2000000
     dur: 15000000
@@ -22,6 +23,7 @@
     process {
       name: "com.google.android.apps.nexuslauncher"
       uid: 10002
+      pid: 2000
     }
     ts: 2000000
     dur: 15000000
@@ -39,6 +41,7 @@
     process {
       name: "com.google.android.third.process"
       uid: 10003
+      pid: 3000
     }
     ts: 2000000
     dur: 150000000
@@ -147,6 +150,7 @@
     process {
       name: "com.android.systemui"
       uid: 10001
+      pid: 1000
     }
     ts: 20000000
     dur: 10000000
@@ -164,6 +168,7 @@
     process {
       name: "com.android.systemui"
       uid: 10001
+      pid: 1000
     }
     ts: 22000000
     dur: 10000000
@@ -175,4 +180,29 @@
       min_dur_ms: 10
     }
   }
+  cuj {
+    id: 6
+    name: "WITH_NAMED_BINDER_TRANSACTION"
+    process {
+      name: "com.android.systemui"
+      uid: 10001
+      pid: 1000
+    }
+    ts: 40000000
+    dur: 10000000
+    blocking_calls {
+      name: "AIDL::java::IWindowManager::hasNavigationBar::server"
+      cnt: 1
+      total_dur_ms: 10
+      max_dur_ms: 10
+      min_dur_ms: 10
+    }
+    blocking_calls {
+      name: "binder transaction"
+      cnt: 1
+      total_dur_ms: 10
+      max_dur_ms: 10
+      min_dur_ms: 10
+    }
+  }
 }
diff --git a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py b/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py
index 7573b76..1039e47 100755
--- a/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py
+++ b/test/trace_processor/diff_tests/android/android_blocking_calls_cuj_metric.py
@@ -45,6 +45,19 @@
   trace.add_atrace_async_end(ts=ts_end, tid=pid, pid=pid, buf=buf)
 
 
+def add_binder_transaction(trace, tx_pid, rx_pid, start_ts, end_ts):
+  trace.add_binder_transaction(
+      transaction_id=tx_pid,
+      ts_start=start_ts,
+      ts_end=end_ts,
+      tid=tx_pid,
+      pid=tx_pid,
+      reply_id=rx_pid,
+      reply_ts_start=start_ts,
+      reply_ts_end=end_ts,
+      reply_tid=rx_pid,
+      reply_pid=rx_pid)
+
 # Adds a set of predefined blocking calls in places near the cuj boundaries to
 # verify that only the portion inside the cuj is counted in the metric.
 def add_cuj_with_blocking_calls(trace, cuj_name, pid):
@@ -148,6 +161,31 @@
       pid=pid)
 
 
+def add_cuj_with_named_binder_transaction(pid, rx_pid):
+  cuj_begin = 40_000_000
+  cuj_end = 50_000_000
+
+  add_async_trace(
+      trace,
+      ts=cuj_begin,
+      ts_end=cuj_end,
+      buf="L<WITH_NAMED_BINDER_TRANSACTION>",
+      pid=pid)
+
+  add_binder_transaction(
+      trace, tx_pid=pid, rx_pid=rx_pid, start_ts=cuj_begin, end_ts=cuj_end)
+
+  # Slice inside the binder reply, to give a name to the binder call.
+  # The named binder slice introduced should be the length of the entire
+  # transaction even if the "name" slice only covers some of the binder reply.
+  add_main_thread_atrace(
+      trace,
+      ts=cuj_begin + 1_000_000,
+      ts_end=cuj_end - 1_000_000,
+      buf="AIDL::java::IWindowManager::hasNavigationBar::server",
+      pid=rx_pid)
+
+
 def add_process(trace, package_name, uid, pid):
   trace.add_package_list(ts=0, name=package_name, uid=uid, version_code=1)
   trace.add_process(
@@ -180,6 +218,8 @@
 add_overlapping_cujs_with_blocking_calls(trace, pid=SYSUI_PID,
                                          start_ts=20_000_000)
 
+add_cuj_with_named_binder_transaction(pid=SYSUI_PID, rx_pid=LAUNCHER_PID)
+
 # Note that J<*> events are not tested here.
 # See test_android_blocking_calls_on_jank_cujs.
 sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/android/android_blocking_calls_on_jank_cuj_metric.out b/test/trace_processor/diff_tests/android/android_blocking_calls_on_jank_cuj_metric.out
index 68b3c9f..89d7c63 100644
--- a/test/trace_processor/diff_tests/android/android_blocking_calls_on_jank_cuj_metric.out
+++ b/test/trace_processor/diff_tests/android/android_blocking_calls_on_jank_cuj_metric.out
@@ -15,6 +15,7 @@
         apk_version_code: 1
         debuggable: false
       }
+      pid: 1000
     }
     ts: 0
     dur: 115000000
@@ -42,6 +43,7 @@
         apk_version_code: 1
         debuggable: false
       }
+      pid: 1000
     }
     ts: 0
     dur: 802000000
diff --git a/test/trace_processor/diff_tests/android/android_network_activity.out b/test/trace_processor/diff_tests/android/android_network_activity.out
new file mode 100644
index 0000000..14418e4
--- /dev/null
+++ b/test/trace_processor/diff_tests/android/android_network_activity.out
@@ -0,0 +1,4 @@
+"package_name","ts","dur","packet_count","packet_length"
+"uid=123",1000,1010,2,100
+"uid=123",3000,2500,4,200
+"uid=456",1005,1010,2,300
diff --git a/test/trace_processor/diff_tests/android/android_system_property_slice.out b/test/trace_processor/diff_tests/android/android_system_property_slice.out
index 8fe8d07..2505d36 100644
--- a/test/trace_processor/diff_tests/android/android_system_property_slice.out
+++ b/test/trace_processor/diff_tests/android/android_system_property_slice.out
@@ -1,3 +1,3 @@
-"id","type","name","id","ts","dur","type","name"
-1,"track","DeviceStateChanged",0,1000,0,"internal_slice","some_state_from_sysprops"
-1,"track","DeviceStateChanged",1,3000,0,"internal_slice","some_state_from_atrace"
+"type","name","id","ts","dur","type","name"
+"track","DeviceStateChanged",0,1000,0,"internal_slice","some_state_from_sysprops"
+"track","DeviceStateChanged",1,3000,0,"internal_slice","some_state_from_atrace"
diff --git a/test/trace_processor/diff_tests/android/tests.py b/test/trace_processor/diff_tests/android/tests.py
index b808d1b..84bafc1 100644
--- a/test/trace_processor/diff_tests/android/tests.py
+++ b/test/trace_processor/diff_tests/android/tests.py
@@ -58,14 +58,14 @@
         }
         """),
         query="""
-        SELECT t.id, t.type, t.name, c.id, c.ts, c.type, c.value
+        SELECT t.type, t.name, c.id, c.ts, c.type, c.value
         FROM counter_track t JOIN counter c ON t.id = c.track_id
         WHERE name = 'ScreenState';
         """,
         out=Csv("""
-        "id","type","name","id","ts","type","value"
-        0,"counter_track","ScreenState",0,1000,"counter",2.000000
-        0,"counter_track","ScreenState",1,2000,"counter",1.000000
+        "type","name","id","ts","type","value"
+        "counter_track","ScreenState",0,1000,"counter",2.000000
+        "counter_track","ScreenState",1,2000,"counter",1.000000
         """))
 
   def test_android_system_property_slice(self):
@@ -105,12 +105,163 @@
         }
         """),
         query="""
-        SELECT t.id, t.type, t.name, s.id, s.ts, s.dur, s.type, s.name
+        SELECT t.type, t.name, s.id, s.ts, s.dur, s.type, s.name
         FROM track t JOIN slice s ON s.track_id = t.id
         WHERE t.name = 'DeviceStateChanged';
         """,
         out=Path('android_system_property_slice.out'))
 
+  def test_android_battery_stats_event_slices(self):
+    # The following has three events
+    # * top (123, mail) from 1000 to 9000 explicit
+    # * job (456, mail_job) starting at 3000 (end is inferred as trace end)
+    # * job (789, video_job) ending at 4000 (start is inferred as trace start)
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          ftrace_events {
+            cpu: 1
+            event {
+              timestamp: 1000
+              pid: 1
+              print {
+                buf: "N|1000|battery_stats.top|+top=123:\"mail\"\n"
+              }
+            }
+            event {
+              timestamp: 3000
+              pid: 1
+              print {
+                buf: "N|1000|battery_stats.job|+job=456:\"mail_job\"\n"
+              }
+            }
+            event {
+              timestamp: 4000
+              pid: 1
+              print {
+                buf: "N|1000|battery_stats.job|-job=789:\"video_job\"\n"
+              }
+            }
+            event {
+              timestamp: 9000
+              pid: 1
+              print {
+                buf: "N|1000|battery_stats.top|-top=123:\"mail\"\n"
+              }
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT IMPORT('android.battery_stats');
+        SELECT * FROM android_battery_stats_event_slices
+        ORDER BY str_value;
+        """,
+        out=Path('android_battery_stats_event_slices.out'))
+
+  def test_android_battery_stats_counters(self):
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          ftrace_events {
+            cpu: 1
+            event {
+              timestamp: 1000
+              pid: 1
+              print {
+                buf: "C|1000|battery_stats.data_conn|13\n"
+              }
+            }
+            event {
+              timestamp: 4000
+              pid: 1
+              print {
+                buf: "C|1000|battery_stats.data_conn|20\n"
+              }
+            }
+            event {
+              timestamp: 1000
+              pid: 1
+              print {
+                buf: "C|1000|battery_stats.audio|1\n"
+              }
+            }
+          }
+        }
+        """),
+        query="""
+        SELECT IMPORT('android.battery_stats');
+        SELECT * FROM android_battery_stats_state
+        ORDER BY ts, track_name;
+        """,
+        out=Path('android_battery_stats_state.out'))
+
+  def test_android_network_activity(self):
+    # The following should have three activity regions:
+    # * uid=123 from 1000 to 2010 (note: end is max(ts)+idle_ns)
+    # * uid=456 from 1005 to 2015 (note: doesn't group with above due to name)
+    # * uid=123 from 3000 to 5500 (note: gap between 1010 to 3000 > idle_ns)
+    # Note: packet_timestamps are delta encoded from the base timestamp.
+    return DiffTestBlueprint(
+        trace=TextProto(r"""
+        packet {
+          timestamp: 0
+          network_packet_bundle {
+            ctx {
+              direction: DIR_EGRESS
+              interface: "wlan"
+              uid: 123
+            }
+            packet_timestamps: [
+              1000, 1010,
+              3000, 3050, 4000, 4500
+            ],
+            packet_lengths: [
+              50, 50,
+              50, 50, 50, 50
+            ],
+          }
+        }
+        packet {
+          timestamp: 0
+          network_packet_bundle {
+            ctx {
+              direction: DIR_EGRESS
+              interface: "wlan"
+              uid: 456
+            }
+            packet_timestamps: [1005, 1015]
+            packet_lengths: [100, 200]
+          }
+        }
+        packet {
+          timestamp: 0
+          network_packet_bundle {
+            ctx {
+              direction: DIR_INGRESS
+              interface: "loopback"
+              uid: 123
+            }
+            packet_timestamps: [6000]
+            packet_lengths: [100]
+          }
+        }
+        """),
+        query="""
+        SELECT RUN_METRIC(
+          'android/network_activity_template.sql',
+          'view_name', 'android_network_activity',
+          'group_by',  'package_name',
+          'filter',    'iface = "wlan"',
+          'idle_ns',   '1000',
+          'quant_ns',  '100'
+        );
+
+        SELECT * FROM android_network_activity
+        ORDER BY package_name, ts;
+        """,
+        out=Path('android_network_activity.out'))
+
   def test_binder_sync_binder_metrics(self):
     return DiffTestBlueprint(
         trace=DataPath('android_binder_metric_trace.atr'),
@@ -236,15 +387,18 @@
         query="""
       SELECT IMPORT('android.monitor_contention');
       SELECT
-        *
+        blocking_method,
+        blocked_method,
+        short_blocking_method,
+        short_blocked_method
       FROM android_monitor_contention
       WHERE binder_reply_id IS NOT NULL
       ORDER BY dur DESC
       LIMIT 1;
       """,
         out=Csv("""
-        "blocking_method","blocked_method","short_blocking_method","short_blocked_method","blocking_src","blocked_src","waiter_count","blocked_utid","blocked_thread_name","blocking_utid","blocking_thread_name","blocking_tid","upid","process_name","id","ts","dur","track_id","is_blocked_thread_main","is_blocking_thread_main","binder_reply_id","binder_reply_ts","binder_reply_tid"
-        "boolean com.android.server.am.ActivityManagerService.forceStopPackageLocked(java.lang.String, int, boolean, boolean, boolean, boolean, boolean, int, java.lang.String)","boolean com.android.server.am.ActivityManagerService.isUidActive(int, java.lang.String)","com.android.server.am.ActivityManagerService.forceStopPackageLocked","com.android.server.am.ActivityManagerService.isUidActive","ActivityManagerService.java:4484","ActivityManagerService.java:7325",0,656,"binder:642_12",495,"binder:642_1",657,250,"system_server",291,1737056375519,37555955,1235,0,0,285,1737055785896,2720
+        "blocking_method","blocked_method","short_blocking_method","short_blocked_method"
+        "boolean com.android.server.am.ActivityManagerService.forceStopPackageLocked(java.lang.String, int, boolean, boolean, boolean, boolean, boolean, int, java.lang.String)","boolean com.android.server.am.ActivityManagerService.isUidActive(int, java.lang.String)","com.android.server.am.ActivityManagerService.forceStopPackageLocked","com.android.server.am.ActivityManagerService.isUidActive"
       """))
 
   def test_monitor_contention_chain_blocked_functions(self):
@@ -305,7 +459,6 @@
         id,
         ts,
         dur,
-        track_id,
         is_blocked_thread_main,
         is_blocking_thread_main,
         IIF(binder_reply_id IS NULL, "", binder_reply_id) AS binder_reply_id,
@@ -316,8 +469,8 @@
       LIMIT 1;
       """,
         out=Csv("""
-        "parent_id","blocking_method","blocked_method","short_blocking_method","short_blocked_method","blocking_src","blocked_src","waiter_count","blocked_utid","blocked_thread_name","blocking_utid","blocking_thread_name","upid","process_name","id","ts","dur","track_id","is_blocked_thread_main","is_blocking_thread_main","binder_reply_id","binder_reply_ts","binder_reply_tid"
-        "","void com.android.server.am.ActivityManagerService.forceStopPackage(java.lang.String, int)","boolean com.android.server.am.ActivityManagerService.unbindService(android.app.IServiceConnection)","com.android.server.am.ActivityManagerService.forceStopPackage","com.android.server.am.ActivityManagerService.unbindService","ActivityManagerService.java:3992","ActivityManagerService.java:12719",0,640,"StorageUserConn",495,"binder:642_1",250,"system_server",327,1737063410007,46114664,1238,0,0,"","",""
+        "parent_id","blocking_method","blocked_method","short_blocking_method","short_blocked_method","blocking_src","blocked_src","waiter_count","blocked_utid","blocked_thread_name","blocking_utid","blocking_thread_name","upid","process_name","id","ts","dur","is_blocked_thread_main","is_blocking_thread_main","binder_reply_id","binder_reply_ts","binder_reply_tid"
+        "","void com.android.server.am.ActivityManagerService.forceStopPackage(java.lang.String, int)","boolean com.android.server.am.ActivityManagerService.unbindService(android.app.IServiceConnection)","com.android.server.am.ActivityManagerService.forceStopPackage","com.android.server.am.ActivityManagerService.unbindService","ActivityManagerService.java:3992","ActivityManagerService.java:12719",0,640,"StorageUserConn",495,"binder:642_1",250,"system_server",327,1737063410007,46114664,0,0,"","",""
       """))
 
   def test_monitor_contention_metric(self):
diff --git a/test/trace_processor/diff_tests/android/tests_general.py b/test/trace_processor/diff_tests/android/tests_general.py
deleted file mode 100644
index fd555a5..0000000
--- a/test/trace_processor/diff_tests/android/tests_general.py
+++ /dev/null
@@ -1,71 +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
-from python.generators.diff_tests.testing import DiffTestBlueprint
-from python.generators.diff_tests.testing import DiffTestModule
-
-
-class AndroidGeneral(DiffTestModule):
-
-  def test_game_intervention_list(self):
-    return DiffTestBlueprint(
-        trace=Path('game_intervention_list_test.textproto'),
-        query="""
-SELECT
-  package_name,
-  uid,
-  current_mode,
-  standard_mode_supported,
-  standard_mode_downscale,
-  standard_mode_use_angle,
-  standard_mode_fps,
-  perf_mode_supported,
-  perf_mode_downscale,
-  perf_mode_use_angle,
-  perf_mode_fps,
-  battery_mode_supported,
-  battery_mode_downscale,
-  battery_mode_use_angle,
-  battery_mode_fps
-FROM android_game_intervention_list
-ORDER BY package_name;
-""",
-        out=Path('game_intervention_list_test.out'))
-
-  def test_android_system_property_counter(self):
-    return DiffTestBlueprint(
-        trace=Path('android_system_property.textproto'),
-        query="""
-SELECT t.id, t.type, t.name, c.id, c.ts, c.type, c.value
-FROM counter_track t JOIN counter c ON t.id = c.track_id
-WHERE name = 'ScreenState';
-""",
-        out=Csv("""
-"id","type","name","id","ts","type","value"
-0,"counter_track","ScreenState",0,1000,"counter",2.000000
-0,"counter_track","ScreenState",1,2000,"counter",1.000000
-"""))
-
-  def test_android_system_property_slice(self):
-    return DiffTestBlueprint(
-        trace=Path('android_system_property.textproto'),
-        query="""
-SELECT t.id, t.type, t.name, s.id, s.ts, s.dur, s.type, s.name
-FROM track t JOIN slice s ON s.track_id = t.id
-WHERE t.name = 'DeviceStateChanged';
-""",
-        out=Path('android_system_property_slice.out'))
diff --git a/test/trace_processor/diff_tests/chrome/chrome_scroll_check.py b/test/trace_processor/diff_tests/chrome/chrome_scroll_check.py
new file mode 100644
index 0000000..4c51da4
--- /dev/null
+++ b/test/trace_processor/diff_tests/chrome/chrome_scroll_check.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 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.
+
+# 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
+# the case.
+
+from os import sys
+
+import synth_common
+
+from synth_common import ms_to_ns
+trace = synth_common.create_trace()
+
+from chrome_scroll_helper import ChromeScrollHelper
+
+helper = ChromeScrollHelper(trace, start_id=1234, start_gesture_id=5678)
+
+# First scroll
+helper.begin(from_ms=0, dur_ms=10)
+helper.update(from_ms=15, dur_ms=10)
+helper.update(from_ms=30, dur_ms=10)
+helper.end(from_ms=45, dur_ms=10)
+
+# Second scroll
+helper.begin(from_ms=60, dur_ms=10)
+helper.update(from_ms=75, dur_ms=10)
+helper.end(from_ms=90, dur_ms=10)
+
+# Third scroll, won't have a GestureScrollEnd value.
+helper.begin(from_ms=120, dur_ms=10)
+helper.update(from_ms=135, dur_ms=10)
+helper.update(from_ms=150, dur_ms=10)
+helper.update(from_ms=150, dur_ms=10)
+helper.update(from_ms=180, dur_ms=10)
+
+sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/chrome/chrome_scroll_helper.py b/test/trace_processor/diff_tests/chrome/chrome_scroll_helper.py
new file mode 100644
index 0000000..7b7cee1
--- /dev/null
+++ b/test/trace_processor/diff_tests/chrome/chrome_scroll_helper.py
@@ -0,0 +1,66 @@
+#!/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.
+
+# 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
+# the case.
+
+import synth_common
+
+from synth_common import ms_to_ns
+trace = synth_common.create_trace()
+
+
+class ChromeScrollHelper:
+
+  def __init__(self, trace, start_id, start_gesture_id):
+    self.trace = trace
+    self.id = start_id
+    self.gesture_id = start_gesture_id
+
+  def begin(self, from_ms, dur_ms):
+    self.trace.add_input_latency_event_slice(
+        "GestureScrollBegin",
+        ts=ms_to_ns(from_ms),
+        dur=ms_to_ns(dur_ms),
+        track=self.id,
+        trace_id=self.id,
+        gesture_scroll_id=self.gesture_id,
+    )
+    self.id += 1
+
+  def update(self, from_ms, dur_ms, gets_to_gpu=True):
+    self.trace.add_input_latency_event_slice(
+        "GestureScrollUpdate",
+        ts=ms_to_ns(from_ms),
+        dur=ms_to_ns(dur_ms),
+        track=self.id,
+        trace_id=self.id,
+        gesture_scroll_id=self.gesture_id,
+        gets_to_gpu=gets_to_gpu,
+        is_coalesced=False,
+    )
+    self.id += 1
+
+  def end(self, from_ms, dur_ms):
+    self.trace.add_input_latency_event_slice(
+        "GestureScrollEnd",
+        ts=ms_to_ns(from_ms),
+        dur=ms_to_ns(dur_ms),
+        track=self.id,
+        trace_id=self.id,
+        gesture_scroll_id=self.gesture_id)
+    self.id += 1
+    self.gesture_id += 1
diff --git a/test/trace_processor/diff_tests/chrome/chrome_tasks.out b/test/trace_processor/diff_tests/chrome/chrome_tasks.out
index ef33837..7ca510e 100644
--- a/test/trace_processor/diff_tests/chrome/chrome_tasks.out
+++ b/test/trace_processor/diff_tests/chrome/chrome_tasks.out
@@ -16,7 +16,6 @@
 "sendTouchEvent","java",194
 "viz.mojom.CompositorFrameSinkClient message (hash=3114070324)","mojo",178
 "viz.mojom.CompositorFrameSink message (hash=3089589715)","mojo",170
-"RunTask(posted_from=mojo/public/cpp/system/simple_watcher.cc:ArmOrNotify)","scheduler",158
 "RunTask(posted_from=cc/scheduler/scheduler.cc:ScheduleBeginImplFrameDeadline)","scheduler",149
 "RunTask(posted_from=net/quic/quic_chromium_alarm_factory.cc:SetImpl)","scheduler",135
 "RunTask(posted_from=mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:SendMessage)","scheduler",114
@@ -25,6 +24,7 @@
 "blink.mojom.WidgetInputHandler reply (hash=3392143105)","mojo",97
 "tracing.mojom.ProducerHost message (hash=3013694824)","mojo",82
 "RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:WriteDataInternal)","scheduler",81
+"RunTask(posted_from=mojo/public/cpp/system/simple_watcher.cc:ArmOrNotify)","mojo",74
 "RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:CloseInternal)","scheduler",73
 "RunTask(posted_from=base/android/task_scheduler/task_runner_android.cc:PostDelayedTask)","scheduler",63
 "RunTask(posted_from=base/android/application_status_listener.cc:NotifyApplicationStateChange)","scheduler",54
@@ -37,16 +37,16 @@
 "RunTask(posted_from=cc/trees/proxy_impl.cc:ScheduledActionSendBeginMainFrame)","scheduler",47
 "RunTask(posted_from=cc/trees/proxy_main.cc:BeginMainFrame)","scheduler",47
 "network.mojom.URLLoaderClient message (hash=374770486)","mojo",46
+"network.mojom.URLLoaderFactory message (hash=2397174083)","mojo",46
 "viz.mojom.CompositorFrameSink message (hash=1654984935)","mojo",46
 "RunTask(posted_from=components/update_client/component.cc:ChangeState)","scheduler",45
-"network.mojom.URLLoaderFactory message (hash=2397174083)","mojo",45
 "Choreographer(java_views=OmniboxSuggestionsList)","choreographer",44
 "RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:CreateEntryInternal)","scheduler",44
 "network.mojom.ProxyConfigPollerClient message (hash=2347231843)","mojo",43
 "blink.mojom.AssociatedInterfaceProvider message (hash=2648115757)","mojo",37
+"network.mojom.URLLoaderClient message (hash=3734484340)","mojo",35
 "network.mojom.URLLoaderNetworkServiceObserver message (hash=3598259070)","mojo",35
 "Looper.dispatch: com.android.internal.view.IInputConnectionWrapper$MyHandler(null)","other",34
 "RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:OpenOrCreateEntryInternal)","scheduler",34
-"network.mojom.URLLoaderClient message (hash=3734484340)","mojo",34
+"network.mojom.URLLoaderClient message (hash=2503424824)","mojo",32
 "RunTask(posted_from=components/update_client/component.cc:TransitionState)","scheduler",31
-"RunTask(posted_from=net/http/http_stream_factory_job.cc:RunLoop)","scheduler",31
diff --git a/test/trace_processor/diff_tests/chrome/scroll_jank_gpu_check.py b/test/trace_processor/diff_tests/chrome/scroll_jank_gpu_check.py
index 7e33ac2..c9ba45c 100644
--- a/test/trace_processor/diff_tests/chrome/scroll_jank_gpu_check.py
+++ b/test/trace_processor/diff_tests/chrome/scroll_jank_gpu_check.py
@@ -24,51 +24,9 @@
 from synth_common import ms_to_ns
 trace = synth_common.create_trace()
 
+from chrome_scroll_helper import ChromeScrollHelper
 
-class Helper:
-
-  def __init__(self, trace, start_id, start_gesture_id):
-    self.trace = trace
-    self.id = start_id
-    self.gesture_id = start_gesture_id
-
-  def begin(self, from_ms, dur_ms):
-    self.trace.add_input_latency_event_slice(
-        "GestureScrollBegin",
-        ts=ms_to_ns(from_ms),
-        dur=ms_to_ns(dur_ms),
-        track=self.id,
-        trace_id=self.id,
-        gesture_scroll_id=self.gesture_id,
-    )
-    self.id += 1
-
-  def update(self, from_ms, dur_ms, gets_to_gpu=True):
-    self.trace.add_input_latency_event_slice(
-        "GestureScrollUpdate",
-        ts=ms_to_ns(from_ms),
-        dur=ms_to_ns(dur_ms),
-        track=self.id,
-        trace_id=self.id,
-        gesture_scroll_id=self.gesture_id,
-        gets_to_gpu=gets_to_gpu,
-        is_coalesced=False,
-    )
-    self.id += 1
-
-  def end(self, from_ms, dur_ms):
-    self.trace.add_input_latency_event_slice(
-        "GestureScrollEnd",
-        ts=ms_to_ns(from_ms),
-        dur=ms_to_ns(dur_ms),
-        track=self.id,
-        trace_id=self.id,
-        gesture_scroll_id=self.gesture_id)
-    self.id += 1
-    self.gesture_id += 1
-
-
-helper = Helper(trace, start_id=1234, start_gesture_id=5678)
+helper = ChromeScrollHelper(trace, start_id=1234, start_gesture_id=5678)
 
 helper.begin(from_ms=0, dur_ms=10)
 helper.update(from_ms=15, dur_ms=10)
diff --git a/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py b/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py
index c5fd3bc..62833c2 100644
--- a/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py
+++ b/test/trace_processor/diff_tests/chrome/tests_scroll_jank.py
@@ -518,3 +518,25 @@
         30000000,1
         115000000,0
         """))
+
+  def test_chrome_scrolls(self):
+    return DiffTestBlueprint(
+        trace=Path('chrome_scroll_check.py'),
+        query="""
+        SELECT IMPORT('chrome.chrome_scrolls');
+
+        SELECT
+          id,
+          ts,
+          dur,
+          scroll_start_ts,
+          scroll_end_ts
+        FROM chrome_scrolls
+        ORDER by id;
+        """,
+        out=Csv("""
+        "id","ts","dur","scroll_start_ts","scroll_end_ts"
+        5678,0,55000000,0,45000000
+        5679,60000000,40000000,60000000,90000000
+        5680,120000000,70000000,120000000,-1
+        """))
diff --git a/test/trace_processor/diff_tests/fuchsia/tests.py b/test/trace_processor/diff_tests/fuchsia/tests.py
index 54d0773..d7b8b4d 100644
--- a/test/trace_processor/diff_tests/fuchsia/tests.py
+++ b/test/trace_processor/diff_tests/fuchsia/tests.py
@@ -231,3 +231,27 @@
         "Update time(ms)",21
         "Vsync interval",900
         """))
+
+  def test_fuchsia_args_import(self):
+    return DiffTestBlueprint(
+        trace=DataPath('fuchsia_events_and_args.fxt'),
+        query="""
+        SELECT key,int_value,string_value,real_value,value_type,display_value
+        FROM args
+        LIMIT 12;
+        """,
+        out=Csv("""
+        "key","int_value","string_value","real_value","value_type","display_value"
+        "SomeNullArg","[NULL]","null","[NULL]","string","null"
+        "Someuint32",2145,"[NULL]","[NULL]","int","2145"
+        "Someuint64",423621626134123415,"[NULL]","[NULL]","int","423621626134123415"
+        "Someint32",-7,"[NULL]","[NULL]","int","-7"
+        "Someint64",-234516543631231,"[NULL]","[NULL]","int","-234516543631231"
+        "Somedouble","[NULL]","[NULL]",3.141500,"real","3.1415"
+        "ping","[NULL]","pong","[NULL]","string","pong"
+        "somepointer",3285933758964,"[NULL]","[NULL]","pointer","0x2fd10ea19f4"
+        "someotherpointer",43981,"[NULL]","[NULL]","pointer","0xabcd"
+        "somekoid",18,"[NULL]","[NULL]","int","18"
+        "somebool",1,"[NULL]","[NULL]","bool","true"
+        "someotherbool",0,"[NULL]","[NULL]","bool","false"
+        """))
diff --git a/test/trace_processor/diff_tests/graphics/android_jank_cuj.out b/test/trace_processor/diff_tests/graphics/android_jank_cuj.out
index 41cf687..22045da 100644
--- a/test/trace_processor/diff_tests/graphics/android_jank_cuj.out
+++ b/test/trace_processor/diff_tests/graphics/android_jank_cuj.out
@@ -16,6 +16,7 @@
         apk_version_code: 1
         debuggable: false
       }
+      pid: 1000
     }
     ts: 0
     dur: 123000000
@@ -27,6 +28,8 @@
       app_missed: false
       sf_missed: false
       dur_expected: 16000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 2
@@ -36,6 +39,8 @@
       app_missed: true
       sf_missed: true
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 3
@@ -45,6 +50,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 4
@@ -54,6 +61,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 5
@@ -63,6 +72,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 6
@@ -72,6 +83,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     sf_frame {
       frame_number: 1
@@ -127,6 +140,8 @@
       missed_app_frames: 5
       missed_sf_frames: 1
       frame_dur_max: 40000000
+      sf_callback_missed_frames: 0
+      hwui_callback_missed_frames: 0
     }
     timeline_metrics {
       total_frames: 6
@@ -143,6 +158,8 @@
       frame_dur_ms_p90: 34.0
       frame_dur_ms_p95: 37.0
       frame_dur_ms_p99: 39.400000000000006
+      sf_callback_missed_frames: 0
+      hwui_callback_missed_frames: 0
     }
     trace_metrics {
       total_frames: 6
@@ -159,6 +176,8 @@
       frame_dur_ms_p90: 34.0
       frame_dur_ms_p95: 37.0
       frame_dur_ms_p99: 39.400000000000006
+      sf_callback_missed_frames: 0
+      hwui_callback_missed_frames: 0
     }
   }
   cuj {
@@ -178,6 +197,7 @@
         apk_version_code: 1
         debuggable: false
       }
+      pid: 1000
     }
     ts: 0
     dur: 901000010
@@ -189,6 +209,8 @@
       app_missed: false
       sf_missed: false
       dur_expected: 16000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 2
@@ -198,6 +220,8 @@
       app_missed: true
       sf_missed: true
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 3
@@ -207,6 +231,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 4
@@ -216,6 +242,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 5
@@ -225,6 +253,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 6
@@ -234,6 +264,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 7
@@ -243,6 +275,8 @@
       app_missed: false
       sf_missed: true
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 8
@@ -252,6 +286,8 @@
       app_missed: false
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 9
@@ -261,6 +297,8 @@
       app_missed: false
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 10
@@ -270,6 +308,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 11
@@ -279,6 +319,8 @@
       app_missed: true
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 12
@@ -288,6 +330,8 @@
       app_missed: false
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: false
+      hwui_callback_missed: false
     }
     frame {
       frame_number: 13
@@ -297,6 +341,8 @@
       app_missed: false
       sf_missed: false
       dur_expected: 20000000
+      sf_callback_missed: true
+      hwui_callback_missed: true
     }
     sf_frame {
       frame_number: 1
@@ -361,6 +407,8 @@
       missed_sf_frames: 2
       missed_frames_max_successive: 5
       frame_dur_max: 62000000
+      sf_callback_missed_frames: 1
+      hwui_callback_missed_frames: 1
     }
     timeline_metrics {
       total_frames: 13
@@ -377,6 +425,8 @@
       frame_dur_ms_p90: 56.80000000000001
       frame_dur_ms_p95: 61.000000
       frame_dur_ms_p99: 61.000000
+      sf_callback_missed_frames: 1
+      hwui_callback_missed_frames: 1
     }
     trace_metrics {
       total_frames: 13
@@ -393,6 +443,8 @@
       frame_dur_ms_p90: 60.000000
       frame_dur_ms_p95: 61.000000
       frame_dur_ms_p99: 61.000000
+      sf_callback_missed_frames: 1
+      hwui_callback_missed_frames: 1
     }
   }
 }
diff --git a/test/trace_processor/diff_tests/graphics/android_jank_cuj.py b/test/trace_processor/diff_tests/graphics/android_jank_cuj.py
index 830d6ac..beb39f7 100644
--- a/test/trace_processor/diff_tests/graphics/android_jank_cuj.py
+++ b/test/trace_processor/diff_tests/graphics/android_jank_cuj.py
@@ -227,6 +227,17 @@
                                   name="J<SHADE_ROW_EXPAND>")
 trace.add_track_event_slice_end(ts=901_000_010, track=SHADE_CUJ_TRACK)
 add_instant_for_track(trace, ts=11, track=SHADE_CUJ_TRACK, name="FT#layerId#0")
+add_instant_for_track(
+    trace,
+    ts=950_100_000,
+    track=SHADE_CUJ_TRACK,
+    name="FT#MissedHWUICallback#150")
+add_instant_for_track(
+    trace,
+    ts=950_100_000,
+    track=SHADE_CUJ_TRACK,
+    name="FT#MissedSFCallback#150")
+
 trace.add_track_event_slice_begin(
     ts=100_100_000, track=CANCELED_CUJ_TRACK, name="J<CANCELED>")
 trace.add_track_event_slice_end(ts=999_000_000, track=CANCELED_CUJ_TRACK)
diff --git a/test/trace_processor/diff_tests/graphics/frame_missed.py b/test/trace_processor/diff_tests/graphics/frame_missed.py
index d08191d..04469b7 100644
--- a/test/trace_processor/diff_tests/graphics/frame_missed.py
+++ b/test/trace_processor/diff_tests/graphics/frame_missed.py
@@ -28,12 +28,12 @@
 
 trace.add_ftrace_packet(1)
 
-trace.add_print(ts=99, tid=11, buf='C|10|PrevFrameMissed|0')
-trace.add_print(ts=100, tid=11, buf='C|10|PrevFrameMissed|0')
-trace.add_print(ts=101, tid=11, buf='C|10|PrevFrameMissed|1')
-trace.add_print(ts=102, tid=11, buf='C|10|PrevFrameMissed|0')
-trace.add_print(ts=103, tid=11, buf='C|10|PrevFrameMissed|1')
-trace.add_print(ts=104, tid=11, buf='C|10|PrevFrameMissed|1')
-trace.add_print(ts=105, tid=11, buf='C|10|PrevFrameMissed|0')
+trace.add_print(ts=99, tid=11, buf='C|10|PrevFrameMissed 101|0')
+trace.add_print(ts=100, tid=11, buf='C|10|PrevFrameMissed 102|0')
+trace.add_print(ts=101, tid=11, buf='C|10|PrevFrameMissed 102|1')
+trace.add_print(ts=102, tid=11, buf='C|10|PrevFrameMissed 102|0')
+trace.add_print(ts=103, tid=11, buf='C|10|PrevFrameMissed 101|1')
+trace.add_print(ts=104, tid=11, buf='C|10|PrevFrameMissed 101|1')
+trace.add_print(ts=105, tid=11, buf='C|10|PrevFrameMissed 101|0')
 
 sys.stdout.buffer.write(trace.trace.SerializeToString())
diff --git a/test/trace_processor/diff_tests/graphics/tests.py b/test/trace_processor/diff_tests/graphics/tests.py
index aea2818..7ab6c89 100644
--- a/test/trace_processor/diff_tests/graphics/tests.py
+++ b/test/trace_processor/diff_tests/graphics/tests.py
@@ -111,6 +111,20 @@
           missed_gpu_frames: 0
           missed_frame_rate: 0.42857142857142855 # = 3/7
           gpu_invocations: 0
+          metrics_per_display: {
+            display_id: "101"
+            missed_frames: 2
+            missed_hwc_frames: 0
+            missed_gpu_frames: 0
+            missed_frame_rate: 0.5
+          }
+          metrics_per_display: {
+            display_id: "102"
+            missed_frames: 1
+            missed_hwc_frames: 0
+            missed_gpu_frames: 0
+            missed_frame_rate: 0.33333333333333333
+          }
         }
         """))
 
diff --git a/test/trace_processor/diff_tests/parsing/sched_waking_raw_test.sql b/test/trace_processor/diff_tests/parsing/sched_waking_raw_test.sql
index 7fe9c9d..481f3f5 100644
--- a/test/trace_processor/diff_tests/parsing/sched_waking_raw_test.sql
+++ b/test/trace_processor/diff_tests/parsing/sched_waking_raw_test.sql
@@ -13,4 +13,7 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
-SELECT ts, name, cpu, key, int_value, string_value FROM raw JOIN args ON raw.arg_set_id = args.arg_set_id WHERE name = "sched_waking" ORDER BY cpu ASC, ts ASC;
+SELECT ts, name, cpu, key, int_value, string_value
+FROM ftrace_event JOIN args USING(arg_set_id)
+WHERE name = "sched_waking"
+ORDER BY cpu ASC, ts ASC;
diff --git a/test/trace_processor/diff_tests/parsing/tests.py b/test/trace_processor/diff_tests/parsing/tests.py
index 609bdf1..41abe1c 100644
--- a/test/trace_processor/diff_tests/parsing/tests.py
+++ b/test/trace_processor/diff_tests/parsing/tests.py
@@ -175,7 +175,7 @@
         trace=DataPath('lmk_userspace.pb'),
         query="""
         SELECT to_ftrace(id)
-        FROM raw;
+        FROM ftrace_event;
         """,
         out=Path('print_systrace_lmk_userspace.out'))
 
@@ -272,7 +272,7 @@
         trace=Path('print_systrace_unsigned.py'),
         query="""
         SELECT to_ftrace(id)
-        FROM raw;
+        FROM ftrace_event;
         """,
         out=Path('print_systrace_unsigned.out'))
 
@@ -299,7 +299,7 @@
         """),
         query="""
         SELECT to_ftrace(id)
-        FROM raw;
+        FROM ftrace_event;
         """,
         out=Path('cgroup_attach_task_pre_s_print_systrace.out'))
 
@@ -326,7 +326,7 @@
         """),
         query="""
         SELECT to_ftrace(id)
-        FROM raw;
+        FROM ftrace_event;
         """,
         out=Path('cgroup_attach_task_post_s_print_systrace.out'))
 
@@ -919,7 +919,7 @@
         trace=Path('sched_blocked_reason_symbolized.textproto'),
         query="""
         SELECT to_ftrace(id) AS line
-        FROM raw;
+        FROM ftrace_event;
         """,
         out=Path('sched_blocked_reason_symbolized_to_systrace.out'))
 
diff --git a/test/trace_processor/diff_tests/performance/frame_timeline_metric.out b/test/trace_processor/diff_tests/performance/frame_timeline_metric.out
index eac9136..1a034f0 100644
--- a/test/trace_processor/diff_tests/performance/frame_timeline_metric.out
+++ b/test/trace_processor/diff_tests/performance/frame_timeline_metric.out
@@ -6,6 +6,7 @@
   process {
     process {
       name: "process1"
+      pid: 1001
     }
     total_frames: 2
     missed_frames: 2
@@ -27,6 +28,7 @@
   process {
     process {
       name: "process2"
+      pid: 1002
     }
     total_frames: 2
     missed_frames: 1
@@ -48,6 +50,7 @@
   process {
     process {
       name: "process3"
+      pid: 1003
     }
     total_frames: 2
     missed_frames: 2
@@ -69,6 +72,7 @@
   process {
     process {
       name: "process4"
+      pid: 1004
     }
     total_frames: 5
     missed_frames: 4
diff --git a/test/trace_processor/diff_tests/power/tests_power_rails.py b/test/trace_processor/diff_tests/power/tests_power_rails.py
index 869604c..5085cd2 100644
--- a/test/trace_processor/diff_tests/power/tests_power_rails.py
+++ b/test/trace_processor/diff_tests/power/tests_power_rails.py
@@ -58,18 +58,18 @@
     return DiffTestBlueprint(
         trace=Path('power_rails.textproto'),
         query="""
-        SELECT ts, value, t.name AS name
+        SELECT ts, extract_arg(arg_set_id,'packet_ts') as packet_ts, value, t.name AS name
         FROM counter c JOIN counter_track t ON t.id = c.track_id
         ORDER BY ts
         LIMIT 20;
         """,
         out=Csv("""
-        "ts","value","name"
-        3000000,333.000000,"power.test_rail_uws"
-        3000000,0.000000,"power.test_rail_uws"
-        3000004,1000.000000,"Testing"
-        3000005,999.000000,"power.test_rail2_uws"
-        5000000,666.000000,"power.test_rail_uws"
+        "ts","packet_ts","value","name"
+        3000000,3000003,333.000000,"power.test_rail_uws"
+        3000000,3000005,0.000000,"power.test_rail_uws"
+        3000004,"[NULL]",1000.000000,"Testing"
+        3000005,3000005,999.000000,"power.test_rail2_uws"
+        5000000,3000005,666.000000,"power.test_rail_uws"
         """))
 
   def test_power_rails_well_known_power_rails(self):
diff --git a/test/trace_processor/diff_tests/profiling/heap_graph.textproto b/test/trace_processor/diff_tests/profiling/heap_graph.textproto
index b3decef..a6e314b 100644
--- a/test/trace_processor/diff_tests/profiling/heap_graph.textproto
+++ b/test/trace_processor/diff_tests/profiling/heap_graph.textproto
@@ -21,7 +21,7 @@
       pid: 2
       rss_anon_kb: 1000
       vm_swap_kb: 0
-      oom_score_adj: 0
+      oom_score_adj: -800
     }
   }
 }
diff --git a/test/trace_processor/diff_tests/profiling/heap_stats_closest_proc.out b/test/trace_processor/diff_tests/profiling/heap_stats_closest_proc.out
index 1ee09bf..9a786535 100644
--- a/test/trace_processor/diff_tests/profiling/heap_stats_closest_proc.out
+++ b/test/trace_processor/diff_tests/profiling/heap_stats_closest_proc.out
@@ -4,6 +4,7 @@
     process {
       name: "proc1"
       uid: 1000
+      pid: 2
     }
     samples {
       ts: 200000000
@@ -28,6 +29,7 @@
     process {
       name: "proc2"
       uid: 1000
+      pid: 3
     }
     samples {
       ts: 1500000000
diff --git a/test/trace_processor/diff_tests/profiling/java_heap_histogram.out b/test/trace_processor/diff_tests/profiling/java_heap_histogram.out
index 206dc7c..c5aaf49 100644
--- a/test/trace_processor/diff_tests/profiling/java_heap_histogram.out
+++ b/test/trace_processor/diff_tests/profiling/java_heap_histogram.out
@@ -3,7 +3,8 @@
     upid: 2
     process {
       name: "system_server"
-      uid: 1000
+      uid: 1000,
+      pid: 2
     }
     samples {
       ts: 10
diff --git a/test/trace_processor/diff_tests/profiling/tests.py b/test/trace_processor/diff_tests/profiling/tests.py
index e56fbff..4c5c2e3 100644
--- a/test/trace_processor/diff_tests/profiling/tests.py
+++ b/test/trace_processor/diff_tests/profiling/tests.py
@@ -116,6 +116,7 @@
             process {
               name: "system_server"
               uid: 1000
+              pid: 2
             }
             mappings {
               path: "[anon: libc_malloc]"
diff --git a/test/trace_processor/diff_tests/profiling/tests_metrics.py b/test/trace_processor/diff_tests/profiling/tests_metrics.py
index cb210cf..9a4ff07 100644
--- a/test/trace_processor/diff_tests/profiling/tests_metrics.py
+++ b/test/trace_processor/diff_tests/profiling/tests_metrics.py
@@ -71,6 +71,7 @@
             process {
               name: "system_server"
               uid: 1000
+              pid: 2
             }
             samples {
               ts: 10
@@ -81,6 +82,7 @@
               obj_count: 6
               reachable_obj_count: 3
               anon_rss_and_swap_size: 4096000
+              oom_score_adj: 0
               roots {
                 root_type: "ROOT_JAVA_FRAME"
                 type_name: "DeobfuscatedA[]"
diff --git a/test/trace_processor/diff_tests/startup/android_startup.out b/test/trace_processor/diff_tests/startup/android_startup.out
index 48fa752..18b879c 100644
--- a/test/trace_processor/diff_tests/startup/android_startup.out
+++ b/test/trace_processor/diff_tests/startup/android_startup.out
@@ -42,7 +42,8 @@
         package_name: "com.google.android.calendar"
         apk_version_code: 123
         debuggable: false
-      }
+      },
+      pid: 3
     }
     report_fully_drawn {
       dur_ns: 198
diff --git a/test/trace_processor/diff_tests/startup/android_startup_attribution.out b/test/trace_processor/diff_tests/startup/android_startup_attribution.out
index e55fe55..ea96296 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_attribution.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_attribution.out
@@ -25,8 +25,8 @@
       }
       dur_ms: 999.9999
       time_dex_open {
-        dur_ns: 20
-        dur_ms: 2e-05
+        dur_ns: 499999845
+        dur_ms: 499.999845
       }
       time_verify_class {
         dur_ns: 40
@@ -45,6 +45,14 @@
         dur_ns: 50
         dur_ms: 5e-05
       }
+      time_dex_open_thread_main {
+        dur_ns: 499999845
+        dur_ms: 499.999845
+      }
+      time_dlopen_thread_main {
+        dur_ns: 2
+        dur_ms: 2e-06
+      }
     }
     activity_hosting_process_count: 1
     process {
@@ -60,6 +68,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 3
     }
     event_timestamps {
       intent_received: 100
@@ -106,8 +115,11 @@
       name: "dl"
       dur_ns: 5
     }
+    dlopen_file: "libandroid.so"
+    dlopen_file: "libandroid2.so"
     startup_type: "hot"
     slow_start_reason: "GC Activity"
+    slow_start_reason: "Main Thread - Time spent in OpenDexFilesFromOat*"
     slow_start_reason: "Main Thread - Binder transactions blocked"
   }
 }
diff --git a/test/trace_processor/diff_tests/startup/android_startup_attribution.py b/test/trace_processor/diff_tests/startup/android_startup_attribution.py
index b921201..59301e0 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_attribution.py
+++ b/test/trace_processor/diff_tests/startup/android_startup_attribution.py
@@ -78,13 +78,22 @@
 
 trace.add_atrace_begin(
     ts=170, pid=APP_PID, tid=APP_TID, buf='OpenDexFilesFromOat(something else)')
-trace.add_atrace_end(ts=175, pid=APP_PID, tid=APP_TID)
+trace.add_atrace_end(ts=5*10**8, pid=APP_PID, tid=APP_TID)
 
 # OpenDex slice outside the startup.
 trace.add_atrace_begin(
     ts=5, pid=APP_PID, tid=APP_TID, buf='OpenDexFilesFromOat(nothing)')
 trace.add_atrace_end(ts=35, pid=APP_PID, tid=APP_TID)
 
+# dlopen slices within the startup.
+trace.add_atrace_begin(
+    ts=166, pid=APP_PID, tid=APP_TID, buf='dlopen: libandroid.so')
+trace.add_atrace_end(ts=167, pid=APP_PID, tid=APP_TID)
+
+trace.add_atrace_begin(
+    ts=168, pid=APP_PID, tid=APP_TID, buf='dlopen: libandroid2.so')
+trace.add_atrace_end(ts=169, pid=APP_PID, tid=APP_TID)
+
 trace.add_atrace_async_end(
     ts=LAUNCH_END_TS,
     tid=SYSTEM_SERVER_TID,
diff --git a/test/trace_processor/diff_tests/startup/android_startup_attribution_slow.out b/test/trace_processor/diff_tests/startup/android_startup_attribution_slow.out
index 368b7f2..4bf3fff 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_attribution_slow.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_attribution_slow.out
@@ -45,6 +45,10 @@
         dur_ns: 50000000000
         dur_ms: 50000
       }
+      time_dex_open_thread_main {
+        dur_ns: 20000000000
+        dur_ms: 20000.0
+      }
     }
     activity_hosting_process_count: 1
     process {
@@ -60,6 +64,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 3
     }
     event_timestamps {
       intent_received: 100000000000
diff --git a/test/trace_processor/diff_tests/startup/android_startup_breakdown.out b/test/trace_processor/diff_tests/startup/android_startup_breakdown.out
index 36aab5f..2f930d3 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_breakdown.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_breakdown.out
@@ -70,6 +70,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 3
     }
     activities {
       name: "com.google.android.calendar.MainActivity"
@@ -107,7 +108,7 @@
     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: "Potential CPU contention with init"
+    slow_start_reason: "Potential CPU contention with another process"
     startup_type: "cold"
   }
 }
diff --git a/test/trace_processor/diff_tests/startup/android_startup_breakdown_slow.out b/test/trace_processor/diff_tests/startup/android_startup_breakdown_slow.out
index 0b17116..4417091 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_breakdown_slow.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_breakdown_slow.out
@@ -70,6 +70,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 3
     }
     activities {
       name: "com.google.android.calendar.MainActivity"
@@ -106,7 +107,7 @@
     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: "Potential CPU contention with init"
+    slow_start_reason: "Potential CPU contention with another process"
     startup_type: "cold"
   }
 }
diff --git a/test/trace_processor/diff_tests/startup/android_startup_lock_contention.out b/test/trace_processor/diff_tests/startup/android_startup_lock_contention.out
index e570b22..ba1311c 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_lock_contention.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_lock_contention.out
@@ -55,6 +55,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 3
     }
     event_timestamps {
       intent_received: 110
diff --git a/test/trace_processor/diff_tests/startup/android_startup_lock_contention_slow.out b/test/trace_processor/diff_tests/startup/android_startup_lock_contention_slow.out
index cd5dc6c..aa4ec05 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_lock_contention_slow.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_lock_contention_slow.out
@@ -55,6 +55,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 3
     }
     event_timestamps {
       intent_received: 110000000000
diff --git a/test/trace_processor/diff_tests/startup/android_startup_minsdk33.out b/test/trace_processor/diff_tests/startup/android_startup_minsdk33.out
index 1723d34..2a8307b 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_minsdk33.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_minsdk33.out
@@ -63,6 +63,7 @@
         apk_version_code: 123
         debuggable: true
       }
+      pid: 3
     }
     event_timestamps {
       intent_received: 220
diff --git a/test/trace_processor/diff_tests/startup/android_startup_process_track.out b/test/trace_processor/diff_tests/startup/android_startup_process_track.out
index 41b26e5..561fb0d 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_process_track.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_process_track.out
@@ -51,6 +51,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 3
     }
     event_timestamps {
       intent_received: 100
@@ -118,6 +119,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 4
     }
     event_timestamps {
       intent_received: 200
diff --git a/test/trace_processor/diff_tests/startup/android_startup_slow.out b/test/trace_processor/diff_tests/startup/android_startup_slow.out
index 3b5d1ab..f791fd6 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_slow.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_slow.out
@@ -43,6 +43,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 3
     }
     report_fully_drawn {
       dur_ns: 198000000000
@@ -66,6 +67,6 @@
     slow_start_reason: "Main Thread - Time spent in Runnable state"
     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: "Potential CPU contention with init"
+    slow_start_reason: "Potential CPU contention with another process"
   }
 }
diff --git a/third_party/.gitignore b/third_party/.gitignore
new file mode 100644
index 0000000..1642854
--- /dev/null
+++ b/third_party/.gitignore
@@ -0,0 +1,3 @@
+clang-format/
+gn/
+ninja/
diff --git a/tools/cpu_profile b/tools/cpu_profile
index 07e795a..1dfdbb7 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v33.1
+# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        7822272,
+        7904536,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/traceconv',
     'sha256':
-        '2b1bae4755ee0dd7f3a8e55653f8a7c344f688ea29700064ef8211c55bb4ae9f',
+        '037f84ac943f3f4d75447c668cc49c966fe3d85eca3a455c958b24fc6a9e314a',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -58,11 +58,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6604056,
+        6554600,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/traceconv',
     'sha256':
-        'c0bd6d1ebe2c61ffeefbd4f01426e9b853c81daf70530be7e78c97a4d3af100c',
+        'eda545ef4fa37fdfa1b47ced7cbbe0aa3c0df9bd161cacd7c78e6c55aef98d20',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -72,11 +72,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8122112,
+        7664384,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/traceconv',
     'sha256':
-        'c4c57d8e7b435822a1437b2dc7f7154f6ff2e197deff1f9284bbd36bbedb004f',
+        '24285e6e0e873d393fa5a993bac18ec8e1ab5fae6f4e3453214e095ef36e4c45',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -86,11 +86,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6692016,
+        5657944,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/traceconv',
     'sha256':
-        'da666eb9f80bcbec4c959f4adf493a59ff89e4106666fe1884291078dba0243b',
+        'c9af3d976f849fc75e96c2c552cb14fcc9eacce6fe7c45c4a8289080b0f66706',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +100,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7575344,
+        7184224,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/traceconv',
     'sha256':
-        '8ca00c39c5ec7bd78576f64c4ab05e663d803b06b36fbddf968825edbe236fca',
+        'c6dc936492d58a40cd8e0b58abc46bd479e0c1c387cd1ba29198a6c9b2000d7a',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -114,55 +114,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        5376396,
+        5325260,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/traceconv',
     'sha256':
-        'b77e7f0274ba45ff32d34df347845bc996763291fcc6b2a697f56c0c9a543150'
+        '963267dcb58cdde9f61a952e5cb7f3557833209d3251e7fdcefc3b52db54f77b'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        6793744,
+        6572688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/traceconv',
     'sha256':
-        'e83f3d43f8782cb57e9d3e8a3cd31826c9713da9f92bd8d8be2c48872ed423eb'
+        '87373c351fe5e947826cd957438cab8a37a352bf83b1cbbb15fe276eee9d873a'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        7694692,
+        7303588,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/traceconv',
     'sha256':
-        'c9ee2c3c91d6c68cb7f52a626767bde5e267f34c6ddf987ff73eec3d813c0a2c'
+        'dfc4e714963b5ed662d29d6028ffa69e67f8cd2f9a28223f715437a260fd456f'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        7940680,
+        7482056,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/traceconv',
     'sha256':
-        'f75122ca3e6bbe393b705c3bc5514d81c57f38bf408d857d89c4268b79a39e08'
+        '79c666c629fcffd810635270b45e58b40ed253d22650f41550057e5d8f8c49a7'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        7239168,
+        7072768,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/windows-amd64/traceconv.exe',
     'sha256':
-        '5be5d698f69d44b4baa8ae1f21955becad9d0e6774e967f923b8386744002cfe',
+        '40fac80fdeae443a924e160650c94629e6463c1fb5a4f04f4ef6e9e5e72a3965',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/gen_amalgamated b/tools/gen_amalgamated
index c529155..fac7f60 100755
--- a/tools/gen_amalgamated
+++ b/tools/gen_amalgamated
@@ -44,6 +44,7 @@
 # line).
 gn_args = ' '.join([
     'enable_perfetto_ipc=true',
+    'enable_perfetto_zlib=false',
     'is_debug=false',
     'is_perfetto_build_generator=true',
     'is_perfetto_embedder=true',
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index d0eb99f..5317914 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -819,10 +819,9 @@
   module.cmd = ' '.join([
       f'$(location {bp_binary_module_name})',
       '--gen-dir=$(genDir)',
+      '--relative-input-dir=external/perfetto',
       '--inputs',
       '$(in)',
-      '--outputs',
-      '$(out)',
   ])
   module.out.update(target.outputs)
   module.genrule_headers.add(module.name)
diff --git a/tools/gen_bazel b/tools/gen_bazel
index a514fe3..7461e4d 100755
--- a/tools/gen_bazel
+++ b/tools/gen_bazel
@@ -76,16 +76,23 @@
 # exported publicly.
 default_targets = [
     '//src/base:perfetto_base_default_platform',
+    '//src/cloud_trace_processor:cloud_trace_processor',
     '//src/ipc:perfetto_ipc',
     '//src/ipc/protoc_plugin:ipc_plugin',
     '//src/protozero:protozero',
-    '//src/protozero/protoc_plugin:protozero_plugin',
     '//src/protozero/protoc_plugin:cppgen_plugin',
-    '//test:client_api_example',
+    '//src/protozero/protoc_plugin:protozero_plugin',
     '//src/tools/proto_filter:proto_filter',
     '//src/tools/proto_merger:proto_merger',
+    '//test:client_api_example',
 ] + public_targets
 
+# Proto targets are required by internal build rules but don't need to be
+# exported publicly.
+proto_default_targets = [
+  '//protos/perfetto/cloud_trace_processor:lite'
+]
+
 # Proto target groups which will be made public.
 proto_groups = {
     'config': {
@@ -582,6 +589,9 @@
   label = BazelLabel(get_bazel_label_name(target.name), 'perfetto_cc_tp_tables')
   label.comment = target.name
   label.srcs += (gn_utils.label_to_path(x) for x in target.sources)
+  label.deps += sorted(':' + get_bazel_label_name(x.name)
+                       for x in target.transitive_deps
+                       if x.name not in default_python_targets)
   label.outs += target.outputs
   return [label]
 
@@ -784,6 +794,10 @@
       continue
     gn.get_target(re.sub('(lite|zero|cpp|ipc)$', 'source_set', target.name))
 
+  # Discover all the default proto targets so it will be generated next.
+  for target in sorted(proto_default_targets):
+    gn.get_target(target)
+
   # Generate targets for the transitive set of proto targets.
   labels = [
       l for target in sorted(itervalues(gn.proto_libs))
diff --git a/tools/gen_binary_descriptors b/tools/gen_binary_descriptors
index 952e809..e09a5c4 100755
--- a/tools/gen_binary_descriptors
+++ b/tools/gen_binary_descriptors
@@ -21,7 +21,6 @@
 import argparse
 import tempfile
 import subprocess
-import hashlib
 from compat import iteritems
 
 SOURCE_TARGET = [
@@ -36,13 +35,6 @@
 SCRIPT_PATH = 'tools/gen_binary_descriptors'
 
 
-def hash_path(path):
-  hash = hashlib.sha1()
-  with open(os.path.join(ROOT_DIR, path), 'rb') as f:
-    hash.update(f.read())
-  return hash.hexdigest()
-
-
 def find_protoc():
   for root, _, files in os.walk(os.path.join(ROOT_DIR, 'out')):
     if 'protoc' in files:
@@ -50,26 +42,7 @@
   return None
 
 
-def check(source, target):
-  assert os.path.exists(os.path.join(ROOT_DIR, target)), \
-      'Output file {} does not exist and so cannot be checked'.format(target)
-
-  sha1_file = target + '.sha1'
-  assert os.path.exists(sha1_file), \
-      'SHA1 file {} does not exist and so cannot be checked'.format(sha1_file)
-
-  with open(sha1_file, 'rb') as f:
-    s = f.read()
-
-  hashes = re.findall(r'// SHA1\((.*)\)\n// (.*)\n', s.decode())
-  assert sorted([SCRIPT_PATH, source]) == sorted([key for key, _ in hashes])
-  for path, expected_sha1 in hashes:
-    actual_sha1 = hash_path(os.path.join(ROOT_DIR, path))
-    assert actual_sha1 == expected_sha1, \
-        'In {} hash given for {} did not match'.format(target, path)
-
-
-def generate(source, target, protoc_path):
+def generate(source, target, protoc_path, check_only):
   # delete=False + manual unlink is required for Windows. Otherwise the temp
   # file is kept locked exclusively and unaccassible until it's destroyed.
   with tempfile.NamedTemporaryFile(delete=False) as fdescriptor:
@@ -86,23 +59,17 @@
     s = fdescriptor.read()
     fdescriptor.close()
     os.remove(fdescriptor.name)
+
+    if check_only:
+      with open(target, 'rb') as old:
+        old_content = old.read()
+        if (s != old_content):
+          raise AssertionError('Target {} does not match', target)
+      return
+
     with open(target, 'wb') as out:
       out.write(s)
 
-    sha1_path = target + '.sha1'
-    with open(sha1_path, 'wb') as c:
-      c.write("""
-// SHA1({script_path})
-// {script_hash}
-// SHA1({source_path})
-// {source_hash}
-  """.format(
-          script_path=SCRIPT_PATH,
-          script_hash=hash_path(__file__),
-          source_path=source,
-          source_hash=hash_path(os.path.join(source)),
-      ).encode())
-
 
 def main():
   parser = argparse.ArgumentParser()
@@ -112,15 +79,12 @@
 
   try:
     for source, target in SOURCE_TARGET:
-      if args.check_only:
-        check(source, target)
-      else:
-        protoc = args.protoc or find_protoc()
-        assert protoc, 'protoc not found specific (--protoc PROTOC_PATH)'
-        assert os.path.exists(protoc), '{} does not exist'.format(protoc)
-        if protoc is not args.protoc:
-          print('Using protoc: {}'.format(protoc))
-        generate(source, target, protoc)
+      protoc = args.protoc or find_protoc()
+      assert protoc, 'protoc not found specific (--protoc PROTOC_PATH)'
+      assert os.path.exists(protoc), '{} does not exist'.format(protoc)
+      if protoc is not args.protoc:
+        print('Using protoc: {}'.format(protoc))
+      generate(source, target, protoc, args.check_only)
   except AssertionError as e:
     if not str(e):
       raise
diff --git a/tools/gen_tp_table_docs.py b/tools/gen_tp_table_docs.py
index 8b8565a..04339bf 100755
--- a/tools/gen_tp_table_docs.py
+++ b/tools/gen_tp_table_docs.py
@@ -27,6 +27,7 @@
 
 #pylint: disable=wrong-import-position
 from python.generators.trace_processor_table.public import ColumnDoc
+from python.generators.trace_processor_table.public import ColumnFlag
 import python.generators.trace_processor_table.util as util
 from python.generators.trace_processor_table.util import ParsedTable
 from python.generators.trace_processor_table.util import ParsedColumn
@@ -43,6 +44,10 @@
   if table.table.tabledoc.skip_id_and_type and is_skippable_col:
     return None
 
+  # Ignore hidden columns in the documentation.
+  if ColumnFlag.HIDDEN in col.column.flags:
+    return None
+
   # Our default assumption is the documentation for a column is a plain string
   # so just make the comment for the column equal to that.
 
@@ -59,7 +64,9 @@
     raise Exception('Unknown column documentation type '
                     f'{table.table.class_name}::{col.column.name}')
 
-  parsed_type = table.parse_type(col.column.type)
+  parsed_type = util.parse_type_with_cols(table.table,
+                                          [c.column for c in table.columns],
+                                          col.column.type)
   docs_type = parsed_type.cpp_type
   if docs_type == 'StringPool::Id':
     docs_type = 'string'
@@ -88,11 +95,20 @@
   parser = argparse.ArgumentParser()
   parser.add_argument('--out', required=True)
   parser.add_argument('inputs', nargs='*')
+  parser.add_argument('--relative-input-dir')
   args = parser.parse_args()
 
-  tables = util.parse_tables_from_files(args.inputs)
+  def get_relin_path(in_path: str):
+    if not args.relative_input_dir:
+      return in_path
+    return os.path.relpath(in_path, args.relative_input_dir)
+
+  modules = [
+      os.path.splitext(get_relin_path(i).replace('/', '.'))[0]
+      for i in args.inputs
+  ]
   table_docs = []
-  for parsed in tables:
+  for parsed in util.parse_tables_from_modules(modules):
     table = parsed.table
     doc = table.tabledoc
     assert doc
diff --git a/tools/gen_tp_table_headers.py b/tools/gen_tp_table_headers.py
index 1ce0963..f15245e 100755
--- a/tools/gen_tp_table_headers.py
+++ b/tools/gen_tp_table_headers.py
@@ -28,63 +28,71 @@
 
 #pylint: disable=wrong-import-position
 from python.generators.trace_processor_table.serialize import serialize_header
+from python.generators.trace_processor_table.util import find_table_deps
 from python.generators.trace_processor_table.util import ParsedTable
-from python.generators.trace_processor_table.util import parse_tables_from_files
+from python.generators.trace_processor_table.util import parse_tables_from_modules
 #pylint: enable=wrong-import-position
 
+# Suffix which replaces the .py extension for all input modules.
+OUT_HEADER_SUFFIX = '_py.h'
+
 
 @dataclass
 class Header:
   """Represents a Python module which will be converted to a header."""
-  out_path: str
-  relout_path: str
   tables: List[ParsedTable]
 
 
 def main():
   """Main function."""
   parser = argparse.ArgumentParser()
-  parser.add_argument('--gen-dir', required=True)
   parser.add_argument('--inputs', required=True, nargs='*')
-  parser.add_argument('--outputs', required=True, nargs='*')
-  parser.add_argument('--header-prefix')
+  parser.add_argument('--gen-dir', required=True)
+  parser.add_argument('--relative-input-dir')
+  parser.add_argument('--import-prefix', default='')
   args = parser.parse_args()
 
-  if len(args.inputs) != len(args.outputs):
-    raise Exception('Number of inputs must match number of outputs')
+  def get_relin_path(in_path: str):
+    if not args.relative_input_dir:
+      return in_path
+    return os.path.relpath(in_path, args.relative_input_dir)
 
-  header_prefix = args.header_prefix if args.header_prefix else ''
-  in_to_out = dict(zip(args.inputs, args.outputs))
+  def get_relout_path(in_path: str):
+    return os.path.splitext(in_path)[0] + OUT_HEADER_SUFFIX
+
+  def get_out_path(in_path: str):
+    return os.path.join(args.gen_dir, get_relout_path(in_path))
+
+  def get_header_path(in_path: str):
+    return os.path.join(args.import_prefix, get_relout_path(in_path))
+
+  modules = [
+      os.path.splitext(get_relin_path(i).replace(os.sep, '.'))[0]
+      for i in args.inputs
+  ]
   headers: Dict[str, Header] = {}
-  for table in parse_tables_from_files(args.inputs):
-    out_path = in_to_out[table.input_path]
-    relout_path = os.path.join(header_prefix,
-                               os.path.relpath(out_path, args.gen_dir))
-
-    header = headers.get(table.input_path, Header(out_path, relout_path, []))
+  for table in parse_tables_from_modules(modules):
+    input_path = os.path.relpath(table.table.python_module, ROOT_DIR)
+    header = headers.get(input_path, Header([]))
     header.tables.append(table)
-    headers[table.input_path] = header
+    headers[input_path] = header
 
-  # Build a mapping from table class name to the output path of the header
-  # which will be generated for it. This is used to include one header into
-  # another for Id dependencies.
-  table_class_name_to_relout: Dict[str, str] = {}
-  for header in headers.values():
-    for table in header.tables:
-      table_class_name_to_relout[table.table.class_name] = header.relout_path
+  for in_path, header in headers.items():
+    out_path = get_out_path(in_path)
+    relout_path = get_relout_path(in_path)
 
-  for header in headers.values():
     # Find all headers depended on by this table. These will be #include-ed when
     # generating the header file below so ensure we remove ourself.
     header_relout_deps: Set[str] = set()
     for table in header.tables:
-      header_relout_deps = header_relout_deps.union(
-          [table_class_name_to_relout[c] for c in table.find_table_deps()])
-    header_relout_deps.discard(header.relout_path)
+      header_relout_deps = header_relout_deps.union([
+          get_header_path(os.path.relpath(c.python_module, ROOT_DIR))
+          for c in find_table_deps(table.table)
+      ])
+    header_relout_deps.discard(relout_path)
 
-    with open(header.out_path, 'w', encoding='utf8') as out:
-      ifdef_guard = re.sub(r'[^a-zA-Z0-9_-]', '_',
-                           header.relout_path).upper() + '_'
+    with open(out_path, 'w', encoding='utf8') as out:
+      ifdef_guard = re.sub(r'[^a-zA-Z0-9_-]', '_', relout_path).upper() + '_'
       out.write(
           serialize_header(ifdef_guard, header.tables,
                            sorted(header_relout_deps)))
diff --git a/tools/heap_profile b/tools/heap_profile
index 40e7f40..6e32c4b 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v33.1
+# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        7822272,
+        7904536,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/traceconv',
     'sha256':
-        '2b1bae4755ee0dd7f3a8e55653f8a7c344f688ea29700064ef8211c55bb4ae9f',
+        '037f84ac943f3f4d75447c668cc49c966fe3d85eca3a455c958b24fc6a9e314a',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -55,11 +55,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6604056,
+        6554600,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/traceconv',
     'sha256':
-        'c0bd6d1ebe2c61ffeefbd4f01426e9b853c81daf70530be7e78c97a4d3af100c',
+        'eda545ef4fa37fdfa1b47ced7cbbe0aa3c0df9bd161cacd7c78e6c55aef98d20',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -69,11 +69,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8122112,
+        7664384,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/traceconv',
     'sha256':
-        'c4c57d8e7b435822a1437b2dc7f7154f6ff2e197deff1f9284bbd36bbedb004f',
+        '24285e6e0e873d393fa5a993bac18ec8e1ab5fae6f4e3453214e095ef36e4c45',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -83,11 +83,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6692016,
+        5657944,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/traceconv',
     'sha256':
-        'da666eb9f80bcbec4c959f4adf493a59ff89e4106666fe1884291078dba0243b',
+        'c9af3d976f849fc75e96c2c552cb14fcc9eacce6fe7c45c4a8289080b0f66706',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -97,11 +97,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7575344,
+        7184224,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/traceconv',
     'sha256':
-        '8ca00c39c5ec7bd78576f64c4ab05e663d803b06b36fbddf968825edbe236fca',
+        'c6dc936492d58a40cd8e0b58abc46bd479e0c1c387cd1ba29198a6c9b2000d7a',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -111,55 +111,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        5376396,
+        5325260,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/traceconv',
     'sha256':
-        'b77e7f0274ba45ff32d34df347845bc996763291fcc6b2a697f56c0c9a543150'
+        '963267dcb58cdde9f61a952e5cb7f3557833209d3251e7fdcefc3b52db54f77b'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        6793744,
+        6572688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/traceconv',
     'sha256':
-        'e83f3d43f8782cb57e9d3e8a3cd31826c9713da9f92bd8d8be2c48872ed423eb'
+        '87373c351fe5e947826cd957438cab8a37a352bf83b1cbbb15fe276eee9d873a'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        7694692,
+        7303588,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/traceconv',
     'sha256':
-        'c9ee2c3c91d6c68cb7f52a626767bde5e267f34c6ddf987ff73eec3d813c0a2c'
+        'dfc4e714963b5ed662d29d6028ffa69e67f8cd2f9a28223f715437a260fd456f'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        7940680,
+        7482056,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/traceconv',
     'sha256':
-        'f75122ca3e6bbe393b705c3bc5514d81c57f38bf408d857d89c4268b79a39e08'
+        '79c666c629fcffd810635270b45e58b40ed253d22650f41550057e5d8f8c49a7'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        7239168,
+        7072768,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/windows-amd64/traceconv.exe',
     'sha256':
-        '5be5d698f69d44b4baa8ae1f21955becad9d0e6774e967f923b8386744002cfe',
+        '40fac80fdeae443a924e160650c94629e6463c1fb5a4f04f4ef6e9e5e72a3965',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/install-build-deps b/tools/install-build-deps
index 255be95..c72eae9 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -23,6 +23,7 @@
 import stat
 import sys
 import tempfile
+import time
 import zipfile
 
 from collections import namedtuple
@@ -60,6 +61,17 @@
     'buildtools/emsdk',  # Moved to buildtools/{mac,linux64}/emsdk
     'buildtools/test_data',  # Moved to test/data by r.android.com/1539381 .
     'buildtools/d8',  # Removed by r.android.com/1424334 .
+
+    # Build toools moved to third_party/ by r.android.com/2327602 .
+    'buildtools/mac/clang-format',
+    'buildtools/mac/gn',
+    'buildtools/mac/ninja',
+    'buildtools/linux64/clang-format',
+    'buildtools/linux64/gn',
+    'buildtools/linux64/ninja',
+    'buildtools/win/clang-format.exe',
+    'buildtools/win/gn.exe',
+    'buildtools/win/ninja.exe',
 ]
 
 # Dependencies required to build code on the host or when targeting desktop OS.
@@ -67,22 +79,22 @@
     # GN. From https://chrome-infra-packages.appspot.com/dl/gn/gn/.
     # git_revision:0725d7827575b239594fbc8fd5192873a1d62f44 .
     Dependency(
-        'buildtools/mac/gn',
+        'third_party/gn/gn',
         'https://storage.googleapis.com/perfetto/gn-mac-1968-0725d782',
         '9ced623a664560bba38bbadb9b91158ca4186358c847e17ab7d982b351373c2e',
         'darwin', 'x64'),
     Dependency(
-        'buildtools/mac/gn',
+        'third_party/gn/gn',
         'https://storage.googleapis.com/perfetto/gn-mac-arm64-1968-0725d782',
         'd22336b5210b4dad5e36e8c28ce81187f491822cf4d8fd0a257b30d6bee3fd3f',
         'darwin', 'arm64'),
     Dependency(
-        'buildtools/linux64/gn',
+        'third_party/gn/gn',
         'https://storage.googleapis.com/perfetto/gn-linux64-1968-0725d782',
         'f706aaa0676e3e22f5fc9ca482295d7caee8535d1869f99efa2358177b64f5cd',
         'linux', 'x64'),
     Dependency(
-        'buildtools/win/gn.exe',
+        'third_party/gn/gn.exe',
         'https://storage.googleapis.com/perfetto/gn-win-1968-0725d782',
         '001f777f023c7a6959c778fb3a6b6cfc63f6baef953410ecdeaec350fb12285b',
         'windows', 'x64'),
@@ -90,19 +102,19 @@
     # clang-format
     # From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/mac/clang-format.sha1
     Dependency(
-        'buildtools/mac/clang-format',
+        'third_party/clang-format/clang-format',
         'https://storage.googleapis.com/chromium-clang-format/62bde1baa7196ad9df969fc1f06b66360b1a927b',
         '6df686a937443cbe6efc013467a7ba5f98d3f187eb7765bb7abc6ce47626cf66',
         'darwin', 'all'),
     # From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/linux64/clang-format.sha1
     Dependency(
-        'buildtools/linux64/clang-format',
+        'third_party/clang-format/clang-format',
         'https://storage.googleapis.com/chromium-clang-format/1baf0089e895c989a311b6a38ed94d0e8be4c0a7',
         'd02a97a87e8c28898033aaf5986967b24dc47ebd5b376e1cd93e5009f22cd75e',
         'linux', 'x64'),
     # From https://chromium.googlesource.com/chromium/src/buildtools/+/refs/heads/master/win/clang-format.exe.sha1
     Dependency(
-        'buildtools/win/clang-format.exe',
+        'third_party/clang-format/clang-format.exe',
         'https://storage.googleapis.com/chromium-clang-format/d4afd4eba27022f5f6d518133aebde57281677c9',
         '2ba1b4d3ade90ea80316890b598ab5fc16777572be26afec6ce23117da121b80',
         'windows', 'x64'),
@@ -115,17 +127,17 @@
 
     # Ninja
     Dependency(
-        'buildtools/mac/ninja',
+        'third_party/ninja/ninja',
         'https://storage.googleapis.com/perfetto/ninja-mac-x64_and_arm64-182',
         '36e8b7aaa06911e1334feb664dd731a1cd69a15eb916a231a3d10ff65fca2c73',
         'darwin', 'all'),
     Dependency(
-        'buildtools/linux64/ninja',
+        'third_party/ninja/ninja',
         'https://storage.googleapis.com/perfetto/ninja-linux64-182',
         '54ac6a01362190aaabf4cf276f9c8982cdf11b225438940fdde3339be0f2ecdc',
         'linux', 'x64'),
     Dependency(
-        'buildtools/win/ninja.exe',
+        'third_party/ninja/ninja.exe',
         'https://storage.googleapis.com/perfetto/ninja-win-182',
         '09ced0fcd1a4dec7d1b798a2cf9ce5d20e5d2fbc2337343827f192ce47d0f491',
         'windows', 'x64'),
@@ -399,8 +411,22 @@
 TEST_DATA_SCRIPT = os.path.join(TOOLS_DIR, 'test_data')
 
 
+def CheckCallRetry(*args, **kwargs):
+  """ Like subprocess.check_call, with retries up to 5 times. """
+  MAX_ATTEMPTS = 5
+  for attempt in range(1, MAX_ATTEMPTS + 1):
+    try:
+      return subprocess.check_call(*args, **kwargs)
+    except subprocess.CalledProcessError as error:
+      if attempt == MAX_ATTEMPTS:
+        raise error
+      else:
+        logging.error(error)
+        time.sleep(attempt * 3)
+
+
 def DownloadURL(url, out_file):
-  subprocess.check_call(['curl', '-L', '-#', '-o', out_file, url])
+  CheckCallRetry(['curl', '-L', '-#', '-o', out_file, url])
 
 
 def GetArch():
@@ -458,16 +484,23 @@
 
   if not os.path.exists(path):
     return
+  third_party_path = os.path.abspath(os.path.join(ROOT_DIR, 'third_party'))
   buildtools_path = os.path.abspath(os.path.join(ROOT_DIR, 'buildtools'))
   test_path = os.path.abspath(os.path.join(ROOT_DIR, 'test', 'data'))
   if (not os.path.abspath(path).startswith(buildtools_path) and
-      not os.path.abspath(path).startswith(test_path)):
+      not os.path.abspath(path).startswith(test_path) and
+      not os.path.abspath(path).startswith(third_party_path)):
     # Safety check to prevent that some merge confilct ends up doing some
     # rm -rf / or similar.
-    logging.fatal('Cannot remove %s: outside of buildtools and test/data', path)
+    logging.fatal(
+        'Cannot remove %s: outside of {buildtools, test/data, third_party}',
+        path)
     sys.exit(1)
   logging.info('Removing %s' % path)
-  shutil.rmtree(path, onerror=del_read_only_for_windows)
+  if os.path.isdir(path):
+    shutil.rmtree(path, onerror=del_read_only_for_windows)
+  else:
+    os.remove(path)
 
 
 def CheckoutGitRepo(path, git_url, revision, check_only):
@@ -480,10 +513,10 @@
   MkdirRecursive(path)
   logging.info('Fetching %s @ %s into %s', git_url, revision, path)
   subprocess.check_call(['git', 'init', path], cwd=path)
-  subprocess.check_call(
-      ['git', 'fetch', '--quiet', '--depth', '1', git_url, revision], cwd=path)
+  CheckCallRetry(['git', 'fetch', '--quiet', '--depth', '1', git_url, revision],
+                 cwd=path)
   subprocess.check_call(['git', 'checkout', revision, '--quiet'], cwd=path)
-  subprocess.check_call(
+  CheckCallRetry(
       ['git', 'submodule', 'update', '--init', '--recursive', '--quiet'],
       cwd=path)
   assert (IsGitRepoCheckoutOutAtRevision(path, revision))
@@ -538,6 +571,21 @@
             dep.source_url, dep.checksum, actual_checksum))
 
 
+def CheckDepotToolsIsRecent():
+  gn_py_path = shutil.which('gn.py')
+  if gn_py_path is None:
+    return True  # depot_tools doesn't seem to be installed in the PATH.
+  dt_dir = os.path.abspath(os.path.dirname(gn_py_path))
+  cmd = ['git', '-C', dt_dir, 'merge-base', '--is-ancestor', 'a0cf4321', 'HEAD']
+  git_ret = subprocess.call(cmd, stderr=subprocess.DEVNULL)
+  if git_ret == 0:
+    return True
+  print('\033[91mYour depot_tools revision is too old. Please run:\033[0m')
+  print('git -C %s fetch origin && git -C %s checkout -B main -t origin/main' %
+        (dt_dir, dt_dir))
+  return False
+
+
 def Main():
   parser = argparse.ArgumentParser()
   parser.add_argument(
@@ -569,6 +617,9 @@
     print('Building the UI on Windows is unsupported')
     return 1
 
+  if not CheckDepotToolsIsRecent():
+    return 1
+
   deps = BUILD_DEPS_HOST
   if not args.no_toolchain:
     deps += BUILD_DEPS_TOOLCHAIN_HOST
diff --git a/tools/record_android_trace b/tools/record_android_trace
index 65f215d..a9db452 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v33.1
+# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1415776,
+        1432064,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/tracebox',
     'sha256':
-        '860cccef002f1a7216d301a09b97d7276b8a57c8d85ad1c3aa4697bb115ffca7',
+        '4ceb7646cd99303224ab5e7ff0a9f84c04f3c5466fff65a55dab65171ae9d482',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -54,11 +54,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1309272,
+        1325704,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/tracebox',
     'sha256':
-        '9c079ac561064c33e9bdfe2e23e92fb95c025603e545c1aae31b2bd7de0398ad',
+        '2c560fcce5e19eb692e50487af134e2078347cdb79decba0c572917860528388',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -68,11 +68,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2137040,
+        2155496,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/tracebox',
     'sha256':
-        '9eb9ce1a14432c284fecce7886786bb2555bcb6dfb4f00a2df2885984961a5fc',
+        '10b92180bb461a7e21be3f8b3d4640430a98d0547238ce095709213b378217d2',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -82,11 +82,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1277896,
+        1288764,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/tracebox',
     'sha256':
-        '60c71b39be7e04d9d0278e36e7e4d33c32a03d6cc8a3782a9e5ed2484f3f2082',
+        'fa28950ce2b7a9345fbb9272f2dd04d3d4eb2a87f021df25e1e649840eae60b5',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -96,11 +96,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2065704,
+        2082704,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/tracebox',
     'sha256':
-        'a8d9e9e186b5daf45ec80b975b9a3ad04cb578890beda136b821c80b9cc74995',
+        '85c371d79b8e23d22a293c29e6399dc311d891a6bd85d7eeaf2cb0179c69eb27',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -110,44 +110,44 @@
     'file_name':
         'tracebox',
     'file_size':
-        1161172,
+        1169364,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/tracebox',
     'sha256':
-        'f9dac5df26d471d1cf0aff942d7249da6b4122543e003813203ef128a15f93fd'
+        '40a3f31600f02dea10e290134d5c862e0e717f4f039756889a4e72c60f1591b6'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'tracebox',
     'file_size':
-        1764008,
+        1776296,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/tracebox',
     'sha256':
-        '3b325a09b6efae0939b73d4d74e6e01e3735508ed31b774f3a21765efab95099'
+        '562505fca18b34a97687dc002aeebcbf20acef68c8a8e48bed6d618c20e07c92'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'tracebox',
     'file_size':
-        1755052,
+        1767340,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/tracebox',
     'sha256':
-        '86e31fa7e2b476187a0222ac2cf6a4ee7e5f8fb5b0e019c1349d14534343a581'
+        'eb47eb43ba93403557dd15a61196799e945ec324d96109db2f155fb131f9996a'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'tracebox',
     'file_size':
-        2034344,
+        2054824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/tracebox',
     'sha256':
-        '7692f6ceaa5d2eb9da42486262610a1820a9b31d46255f624407bf712eff021d'
+        'a3ae6d108e041ba368a9770f952772f111865d4eff7c8e4e4e2f653f45017948'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/run_buildtools_binary.py b/tools/run_buildtools_binary.py
index 6fcfe1a..1ce25a7 100644
--- a/tools/run_buildtools_binary.py
+++ b/tools/run_buildtools_binary.py
@@ -46,7 +46,13 @@
 
   cmd = args[0]
   args = args[1:]
-  exe_path = os.path.join(ROOT_DIR, 'buildtools', os_dir, cmd) + ext
+
+  # Some binaries have been migrated to third_party/xxx. Look into that path
+  # first (see b/261398524)
+  exe_path = os.path.join(ROOT_DIR, 'third_party', cmd, cmd) + ext
+  if not os.path.exists(exe_path):
+    exe_path = os.path.join(ROOT_DIR, 'buildtools', os_dir, cmd) + ext
+
   if sys_name == 'windows':
     # execl() behaves oddly on Windows: the spawned process doesn't seem to
     # receive CTRL+C. Use subprocess instead.
diff --git a/tools/trace_processor b/tools/trace_processor
index 96d5fa3..1ed7c63 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v33.1
+# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0
 TRACE_PROCESSOR_SHELL_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8583152,
+        8714576,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/trace_processor_shell',
     'sha256':
-        '35673d3546dec894b5d55147da2fad523a8f5917b42ec1c327c940b82d3ce565',
+        '9bdb89493f0f00db5d3a73166450ac2f6ee830de16415e79c5a0234990caa644',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7303384,
+        7286968,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/trace_processor_shell',
     'sha256':
-        'a4d301cf8c0c01d328a9253d5ba78f4249333d4b04236cf8be0c7dad2a65e7e0',
+        '948536035fbe680b47b94a99d320ff459450738e4aeeb16cef18364f0023622b',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8991600,
+        8576688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/trace_processor_shell',
     'sha256':
-        'e8dd82c1ec73fbbf4165ab0d9cbbb750cff5bcf723a1eab51adc9382bf652361',
+        '493698c81fffcabc340c72831b175962dba5a31dfe8572a6d5af083a116af4f8',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7117104,
+        6125384,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/trace_processor_shell',
     'sha256':
-        'bbfde44ec004815a36cecdc1dbc135f815f46ac6a3989c87cb0c577510c1c8fe',
+        '53f1e27603695cf92d22519993b6eafa9c60957d9cb33bd0b300df8573b87ebb',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8384816,
+        8036288,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/trace_processor_shell',
     'sha256':
-        'c4f9a499e8c443961725448aabc04cd7dc18cb79883f6b8b615fd8f4ed7c8c16',
+        '2a2cda222c9d5e18b638057688babb00a3a975ccd4b7dd65f26211c2cb7767f9',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,55 +107,55 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        5823560,
+        5813384,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/trace_processor_shell',
     'sha256':
-        'ec5d23fc761021fe10a7cdb66d35590dab0216b2305f5163ace98da28b535fb8'
+        'f3ec4c194d0b06af5b296c1c479e6b29090e6b7cc7e58fbd55ca2919a126f0ee'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7474864,
+        7294768,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/trace_processor_shell',
     'sha256':
-        'f4877f51d0fbb8e9ead576e746a7adf7806b5cb2dffc4373a55ceeec21f615ff'
+        'f44f47d4b873ec68b6fa4f4c69a3e5a13d58b4d9cb2ec591fa687d4480c1950b'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8436764,
+        8090716,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x86/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/trace_processor_shell',
     'sha256':
-        '68ad60af32890f903afb7cbee7cc8f0f4f4b18dea7ab077cb1d807ea80053dcb'
+        '5636d8251747376787640bc3a4894ecf3091e4bf3d38b007003e1992fc5792df'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'trace_processor_shell',
     'file_size':
-        8781544,
+        8359784,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/trace_processor_shell',
     'sha256':
-        'edf5efca4cf46ffbd3586592490b14d61758198c7d46c1bc8e083b1ab19382f5'
+        '50440fa055ab998f6cf24f9a9a7388520cc854708735521505e10291bc52f3d0'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        8252928,
+        8130560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        '323a210f857ce840c4d69dfa7f9b0a32501ffa4a856e5667a0916e3f8006a5d0',
+        '5cbcf98e29a2d989523235e11e4e0dade692a295ebf47a6c93a09a050ce9bc91',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/tools/tracebox b/tools/tracebox
index d788baf..bf03fa6 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v33.1
+# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0
 TRACEBOX_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'tracebox',
     'file_size':
-        1415776,
+        1432064,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/tracebox',
     'sha256':
-        '860cccef002f1a7216d301a09b97d7276b8a57c8d85ad1c3aa4697bb115ffca7',
+        '4ceb7646cd99303224ab5e7ff0a9f84c04f3c5466fff65a55dab65171ae9d482',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1309272,
+        1325704,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/tracebox',
     'sha256':
-        '9c079ac561064c33e9bdfe2e23e92fb95c025603e545c1aae31b2bd7de0398ad',
+        '2c560fcce5e19eb692e50487af134e2078347cdb79decba0c572917860528388',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2137040,
+        2155496,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/tracebox',
     'sha256':
-        '9eb9ce1a14432c284fecce7886786bb2555bcb6dfb4f00a2df2885984961a5fc',
+        '10b92180bb461a7e21be3f8b3d4640430a98d0547238ce095709213b378217d2',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1277896,
+        1288764,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/tracebox',
     'sha256':
-        '60c71b39be7e04d9d0278e36e7e4d33c32a03d6cc8a3782a9e5ed2484f3f2082',
+        'fa28950ce2b7a9345fbb9272f2dd04d3d4eb2a87f021df25e1e649840eae60b5',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        2065704,
+        2082704,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/tracebox',
     'sha256':
-        'a8d9e9e186b5daf45ec80b975b9a3ad04cb578890beda136b821c80b9cc74995',
+        '85c371d79b8e23d22a293c29e6399dc311d891a6bd85d7eeaf2cb0179c69eb27',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,44 +107,44 @@
     'file_name':
         'tracebox',
     'file_size':
-        1161172,
+        1169364,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/tracebox',
     'sha256':
-        'f9dac5df26d471d1cf0aff942d7249da6b4122543e003813203ef128a15f93fd'
+        '40a3f31600f02dea10e290134d5c862e0e717f4f039756889a4e72c60f1591b6'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'tracebox',
     'file_size':
-        1764008,
+        1776296,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/tracebox',
     'sha256':
-        '3b325a09b6efae0939b73d4d74e6e01e3735508ed31b774f3a21765efab95099'
+        '562505fca18b34a97687dc002aeebcbf20acef68c8a8e48bed6d618c20e07c92'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'tracebox',
     'file_size':
-        1755052,
+        1767340,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/tracebox',
     'sha256':
-        '86e31fa7e2b476187a0222ac2cf6a4ee7e5f8fb5b0e019c1349d14534343a581'
+        'eb47eb43ba93403557dd15a61196799e945ec324d96109db2f155fb131f9996a'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'tracebox',
     'file_size':
-        2034344,
+        2054824,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/tracebox',
     'sha256':
-        '7692f6ceaa5d2eb9da42486262610a1820a9b31d46255f624407bf712eff021d'
+        'a3ae6d108e041ba368a9770f952772f111865d4eff7c8e4e4e2f653f45017948'
 }]
 
 # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py
diff --git a/tools/traceconv b/tools/traceconv
index f838c69..9298eb3 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: /usr/local/google/home/lalitm/perfetto/tools/roll-prebuilts v33.1
+# This file has been generated by: /Users/hjd/src/perfetto/tools/roll-prebuilts v34.0
 TRACECONV_MANIFEST = [{
     'arch':
         'mac-amd64',
     'file_name':
         'traceconv',
     'file_size':
-        7822272,
+        7904536,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-amd64/traceconv',
     'sha256':
-        '2b1bae4755ee0dd7f3a8e55653f8a7c344f688ea29700064ef8211c55bb4ae9f',
+        '037f84ac943f3f4d75447c668cc49c966fe3d85eca3a455c958b24fc6a9e314a',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -51,11 +51,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6604056,
+        6554600,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/mac-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/mac-arm64/traceconv',
     'sha256':
-        'c0bd6d1ebe2c61ffeefbd4f01426e9b853c81daf70530be7e78c97a4d3af100c',
+        'eda545ef4fa37fdfa1b47ced7cbbe0aa3c0df9bd161cacd7c78e6c55aef98d20',
     'platform':
         'darwin',
     'machine': ['arm64']
@@ -65,11 +65,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        8122112,
+        7664384,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-amd64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-amd64/traceconv',
     'sha256':
-        'c4c57d8e7b435822a1437b2dc7f7154f6ff2e197deff1f9284bbd36bbedb004f',
+        '24285e6e0e873d393fa5a993bac18ec8e1ab5fae6f4e3453214e095ef36e4c45',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -79,11 +79,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        6692016,
+        5657944,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm/traceconv',
     'sha256':
-        'da666eb9f80bcbec4c959f4adf493a59ff89e4106666fe1884291078dba0243b',
+        'c9af3d976f849fc75e96c2c552cb14fcc9eacce6fe7c45c4a8289080b0f66706',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -93,11 +93,11 @@
     'file_name':
         'traceconv',
     'file_size':
-        7575344,
+        7184224,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/linux-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/linux-arm64/traceconv',
     'sha256':
-        '8ca00c39c5ec7bd78576f64c4ab05e663d803b06b36fbddf968825edbe236fca',
+        'c6dc936492d58a40cd8e0b58abc46bd479e0c1c387cd1ba29198a6c9b2000d7a',
     'platform':
         'linux',
     'machine': ['aarch64']
@@ -107,55 +107,55 @@
     'file_name':
         'traceconv',
     'file_size':
-        5376396,
+        5325260,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm/traceconv',
     'sha256':
-        'b77e7f0274ba45ff32d34df347845bc996763291fcc6b2a697f56c0c9a543150'
+        '963267dcb58cdde9f61a952e5cb7f3557833209d3251e7fdcefc3b52db54f77b'
 }, {
     'arch':
         'android-arm64',
     'file_name':
         'traceconv',
     'file_size':
-        6793744,
+        6572688,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-arm64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-arm64/traceconv',
     'sha256':
-        'e83f3d43f8782cb57e9d3e8a3cd31826c9713da9f92bd8d8be2c48872ed423eb'
+        '87373c351fe5e947826cd957438cab8a37a352bf83b1cbbb15fe276eee9d873a'
 }, {
     'arch':
         'android-x86',
     'file_name':
         'traceconv',
     'file_size':
-        7694692,
+        7303588,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x86/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x86/traceconv',
     'sha256':
-        'c9ee2c3c91d6c68cb7f52a626767bde5e267f34c6ddf987ff73eec3d813c0a2c'
+        'dfc4e714963b5ed662d29d6028ffa69e67f8cd2f9a28223f715437a260fd456f'
 }, {
     'arch':
         'android-x64',
     'file_name':
         'traceconv',
     'file_size':
-        7940680,
+        7482056,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/android-x64/traceconv',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/android-x64/traceconv',
     'sha256':
-        'f75122ca3e6bbe393b705c3bc5514d81c57f38bf408d857d89c4268b79a39e08'
+        '79c666c629fcffd810635270b45e58b40ed253d22650f41550057e5d8f8c49a7'
 }, {
     'arch':
         'windows-amd64',
     'file_name':
         'traceconv.exe',
     'file_size':
-        7239168,
+        7072768,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v33.1/windows-amd64/traceconv.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v34.0/windows-amd64/traceconv.exe',
     'sha256':
-        '5be5d698f69d44b4baa8ae1f21955becad9d0e6774e967f923b8386744002cfe',
+        '40fac80fdeae443a924e160650c94629e6463c1fb5a4f04f4ef6e9e5e72a3965',
     'platform':
         'win32',
     'machine': ['amd64']
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 7dc51bd..1bd97da 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -32,7 +32,8 @@
         "pako": "^1.0.11",
         "protobufjs": "^6.9.0",
         "util": "^0.12.3",
-        "uuid": "^9.0.0"
+        "uuid": "^9.0.0",
+        "vega-lite": "^5.9.0"
       },
       "devDependencies": {
         "@rollup/plugin-commonjs": "^24.0.1",
@@ -1569,6 +1570,11 @@
         "@types/har-format": "*"
       }
     },
+    "node_modules/@types/clone": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@types/clone/-/clone-2.1.1.tgz",
+      "integrity": "sha512-BZIU34bSYye0j/BFcPraiDZ5ka6MJADjcDVELGf7glr9K+iE8NYVjFslJFVWzskSxkLLyCrSPScE82/UUoBSvg=="
+    },
     "node_modules/@types/color-convert": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-1.9.0.tgz",
@@ -1601,6 +1607,12 @@
       "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz",
       "integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ=="
     },
+    "node_modules/@types/geojson": {
+      "version": "7946.0.4",
+      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.4.tgz",
+      "integrity": "sha512-MHmwBtCb7OCv1DSivz2UNJXPGU/1btAWRKlqJ2saEhVJkpkvqHMMaOpKg0v4sAbDWSQekHGvPVMM8nQ+Jen03Q==",
+      "peer": true
+    },
     "node_modules/@types/graceful-fs": {
       "version": "4.1.6",
       "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz",
@@ -2164,7 +2176,6 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
       "engines": {
         "node": ">=8"
       }
@@ -2173,7 +2184,6 @@
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
       "dependencies": {
         "color-convert": "^2.0.1"
       },
@@ -2914,6 +2924,14 @@
         "wrap-ansi": "^6.2.0"
       }
     },
+    "node_modules/clone": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+      "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/co": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -2980,6 +2998,15 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/commander": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+      "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+      "peer": true,
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/commondir": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
@@ -3093,6 +3120,229 @@
       "resolved": "src/base/utils",
       "link": true
     },
+    "node_modules/d3-array": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.3.tgz",
+      "integrity": "sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==",
+      "peer": true,
+      "dependencies": {
+        "internmap": "1 - 2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-color": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+      "peer": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-delaunay": {
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+      "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+      "peer": true,
+      "dependencies": {
+        "delaunator": "5"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-dispatch": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+      "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+      "peer": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-dsv": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+      "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+      "peer": true,
+      "dependencies": {
+        "commander": "7",
+        "iconv-lite": "0.6",
+        "rw": "1"
+      },
+      "bin": {
+        "csv2json": "bin/dsv2json.js",
+        "csv2tsv": "bin/dsv2dsv.js",
+        "dsv2dsv": "bin/dsv2dsv.js",
+        "dsv2json": "bin/dsv2json.js",
+        "json2csv": "bin/json2dsv.js",
+        "json2dsv": "bin/json2dsv.js",
+        "json2tsv": "bin/json2dsv.js",
+        "tsv2csv": "bin/dsv2dsv.js",
+        "tsv2json": "bin/dsv2json.js"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-force": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
+      "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+      "peer": true,
+      "dependencies": {
+        "d3-dispatch": "1 - 3",
+        "d3-quadtree": "1 - 3",
+        "d3-timer": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-format": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+      "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+      "peer": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-geo": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz",
+      "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "2.5.0 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-geo-projection": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz",
+      "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==",
+      "peer": true,
+      "dependencies": {
+        "commander": "7",
+        "d3-array": "1 - 3",
+        "d3-geo": "1.12.0 - 3"
+      },
+      "bin": {
+        "geo2svg": "bin/geo2svg.js",
+        "geograticule": "bin/geograticule.js",
+        "geoproject": "bin/geoproject.js",
+        "geoquantize": "bin/geoquantize.js",
+        "geostitch": "bin/geostitch.js"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-hierarchy": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+      "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+      "peer": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-interpolate": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+      "peer": true,
+      "dependencies": {
+        "d3-color": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-path": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+      "peer": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-quadtree": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+      "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+      "peer": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-scale": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "2.10.0 - 3",
+        "d3-format": "1 - 3",
+        "d3-interpolate": "1.2.0 - 3",
+        "d3-time": "2.1.1 - 3",
+        "d3-time-format": "2 - 4"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-shape": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+      "peer": true,
+      "dependencies": {
+        "d3-path": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time-format": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+      "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+      "peer": true,
+      "dependencies": {
+        "d3-time": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+      "peer": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/data-urls": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz",
@@ -3236,6 +3486,15 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/delaunator": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz",
+      "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==",
+      "peer": true,
+      "dependencies": {
+        "robust-predicates": "^3.0.0"
+      }
+    },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -3355,14 +3614,12 @@
     "node_modules/emoji-regex": {
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "dev": true
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
     },
     "node_modules/encoding": {
       "version": "0.1.13",
       "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
       "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
-      "dev": true,
       "optional": true,
       "dependencies": {
         "iconv-lite": "^0.6.2"
@@ -3741,7 +3998,6 @@
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
       "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
-      "dev": true,
       "engines": {
         "node": ">=6"
       }
@@ -4337,8 +4593,7 @@
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
     "node_modules/fast-glob": {
       "version": "3.2.12",
@@ -4371,8 +4626,7 @@
     "node_modules/fast-json-stable-stringify": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
-      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
-      "dev": true
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
     },
     "node_modules/fast-levenshtein": {
       "version": "2.0.6",
@@ -4562,7 +4816,6 @@
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "dev": true,
       "engines": {
         "node": "6.* || 8.* || >= 10.*"
       }
@@ -4995,8 +5248,6 @@
       "version": "0.6.3",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
       "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
-      "dev": true,
-      "optional": true,
       "dependencies": {
         "safer-buffer": ">= 2.1.2 < 3.0.0"
       },
@@ -5115,6 +5366,15 @@
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
+    "node_modules/internmap": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+      "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+      "peer": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/ip": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
@@ -5277,7 +5537,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-      "dev": true,
       "engines": {
         "node": ">=8"
       }
@@ -6358,6 +6617,11 @@
       "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
       "dev": true
     },
+    "node_modules/json-stringify-pretty-compact": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz",
+      "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA=="
+    },
     "node_modules/json5": {
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -6934,7 +7198,6 @@
       "version": "2.6.7",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
       "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
-      "dev": true,
       "dependencies": {
         "whatwg-url": "^5.0.0"
       },
@@ -8274,7 +8537,6 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
       "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
-      "dev": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -8388,6 +8650,12 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/robust-predicates": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz",
+      "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==",
+      "peer": true
+    },
     "node_modules/rollup": {
       "version": "2.79.1",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
@@ -8491,6 +8759,12 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "node_modules/rw": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+      "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+      "peer": true
+    },
     "node_modules/safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -8523,8 +8797,7 @@
     "node_modules/safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-      "dev": true
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
     "node_modules/sane": {
       "version": "4.1.0",
@@ -9590,7 +9863,6 @@
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "dev": true,
       "dependencies": {
         "emoji-regex": "^8.0.0",
         "is-fullwidth-code-point": "^3.0.0",
@@ -9604,7 +9876,6 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
       "dependencies": {
         "ansi-regex": "^5.0.1"
       },
@@ -9886,11 +10157,30 @@
         "node": ">=8.0"
       }
     },
+    "node_modules/topojson-client": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
+      "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
+      "peer": true,
+      "dependencies": {
+        "commander": "2"
+      },
+      "bin": {
+        "topo2geo": "bin/topo2geo",
+        "topomerge": "bin/topomerge",
+        "topoquantize": "bin/topoquantize"
+      }
+    },
+    "node_modules/topojson-client/node_modules/commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "peer": true
+    },
     "node_modules/tr46": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
-      "dev": true
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
     },
     "node_modules/trim-newlines": {
       "version": "3.0.1",
@@ -9910,8 +10200,7 @@
     "node_modules/tslib": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
-      "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
-      "dev": true
+      "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
     },
     "node_modules/type-check": {
       "version": "0.4.0",
@@ -10197,6 +10486,484 @@
         "spdx-expression-parse": "^3.0.0"
       }
     },
+    "node_modules/vega": {
+      "version": "5.25.0",
+      "resolved": "https://registry.npmjs.org/vega/-/vega-5.25.0.tgz",
+      "integrity": "sha512-lr+uj0mhYlSN3JOKbMNp1RzZBenWp9DxJ7kR3lha58AFNCzzds7pmFa7yXPbtbaGhB7Buh/t6n+Bzk3Y0VnF5g==",
+      "peer": true,
+      "dependencies": {
+        "vega-crossfilter": "~4.1.1",
+        "vega-dataflow": "~5.7.5",
+        "vega-encode": "~4.9.2",
+        "vega-event-selector": "~3.0.1",
+        "vega-expression": "~5.1.0",
+        "vega-force": "~4.2.0",
+        "vega-format": "~1.1.1",
+        "vega-functions": "~5.13.2",
+        "vega-geo": "~4.4.1",
+        "vega-hierarchy": "~4.1.1",
+        "vega-label": "~1.2.1",
+        "vega-loader": "~4.5.1",
+        "vega-parser": "~6.2.0",
+        "vega-projection": "~1.6.0",
+        "vega-regression": "~1.2.0",
+        "vega-runtime": "~6.1.4",
+        "vega-scale": "~7.3.0",
+        "vega-scenegraph": "~4.10.2",
+        "vega-statistics": "~1.9.0",
+        "vega-time": "~2.1.1",
+        "vega-transforms": "~4.10.2",
+        "vega-typings": "~0.24.0",
+        "vega-util": "~1.17.2",
+        "vega-view": "~5.11.1",
+        "vega-view-transforms": "~4.5.9",
+        "vega-voronoi": "~4.2.1",
+        "vega-wordcloud": "~4.1.4"
+      }
+    },
+    "node_modules/vega-canvas": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-1.2.7.tgz",
+      "integrity": "sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q==",
+      "peer": true
+    },
+    "node_modules/vega-crossfilter": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-4.1.1.tgz",
+      "integrity": "sha512-yesvlMcwRwxrtAd9IYjuxWJJuAMI0sl7JvAFfYtuDkkGDtqfLXUcCzHIATqW6igVIE7tWwGxnbfvQLhLNgK44Q==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-dataflow": {
+      "version": "5.7.5",
+      "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-5.7.5.tgz",
+      "integrity": "sha512-EdsIl6gouH67+8B0f22Owr2tKDiMPNNR8lEvJDcxmFw02nXd8juimclpLvjPQriqn6ta+3Dn5txqfD117H04YA==",
+      "peer": true,
+      "dependencies": {
+        "vega-format": "^1.1.1",
+        "vega-loader": "^4.5.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-encode": {
+      "version": "4.9.2",
+      "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-4.9.2.tgz",
+      "integrity": "sha512-c3J0LYkgYeXQxwnYkEzL15cCFBYPRaYUon8O2SZ6O4PhH4dfFTXBzSyT8+gh8AhBd572l2yGDfxpEYA6pOqdjg==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "d3-interpolate": "^3.0.1",
+        "vega-dataflow": "^5.7.5",
+        "vega-scale": "^7.3.0",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-event-selector": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-3.0.1.tgz",
+      "integrity": "sha512-K5zd7s5tjr1LiOOkjGpcVls8GsH/f2CWCrWcpKy74gTCp+llCdwz0Enqo013ZlGaRNjfgD/o1caJRt3GSaec4A=="
+    },
+    "node_modules/vega-expression": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-5.1.0.tgz",
+      "integrity": "sha512-u8Rzja/cn2PEUkhQN3zUj3REwNewTA92ExrcASNKUJPCciMkHJEjESwFYuI6DWMCq4hQElQ92iosOAtwzsSTqA==",
+      "dependencies": {
+        "@types/estree": "^1.0.0",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-expression/node_modules/@types/estree": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
+      "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
+    },
+    "node_modules/vega-force": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-4.2.0.tgz",
+      "integrity": "sha512-aE2TlP264HXM1r3fl58AvZdKUWBNOGkIvn4EWyqeJdgO2vz46zSU7x7TzPG4ZLuo44cDRU5Ng3I1eQk23Asz6A==",
+      "peer": true,
+      "dependencies": {
+        "d3-force": "^3.0.0",
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-format": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-1.1.1.tgz",
+      "integrity": "sha512-Rll7YgpYbsgaAa54AmtEWrxaJqgOh5fXlvM2wewO4trb9vwM53KBv4Q/uBWCLK3LLGeBXIF6gjDt2LFuJAUtkQ==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "d3-format": "^3.1.0",
+        "d3-time-format": "^4.1.0",
+        "vega-time": "^2.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-functions": {
+      "version": "5.13.2",
+      "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-5.13.2.tgz",
+      "integrity": "sha512-YE1Xl3Qi28kw3vdXVYgKFMo20ttd3+SdKth1jUNtBDGGdrOpvPxxFhZkVqX+7FhJ5/1UkDoAYs/cZY0nRKiYgA==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "d3-color": "^3.1.0",
+        "d3-geo": "^3.1.0",
+        "vega-dataflow": "^5.7.5",
+        "vega-expression": "^5.1.0",
+        "vega-scale": "^7.3.0",
+        "vega-scenegraph": "^4.10.2",
+        "vega-selections": "^5.4.1",
+        "vega-statistics": "^1.8.1",
+        "vega-time": "^2.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-geo": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-4.4.1.tgz",
+      "integrity": "sha512-s4WeZAL5M3ZUV27/eqSD3v0FyJz3PlP31XNSLFy4AJXHxHUeXT3qLiDHoVQnW5Om+uBCPDtTT1ROx1smGIf2aA==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "d3-color": "^3.1.0",
+        "d3-geo": "^3.1.0",
+        "vega-canvas": "^1.2.7",
+        "vega-dataflow": "^5.7.5",
+        "vega-projection": "^1.6.0",
+        "vega-statistics": "^1.8.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-hierarchy": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-4.1.1.tgz",
+      "integrity": "sha512-h5mbrDtPKHBBQ9TYbvEb/bCqmGTlUX97+4CENkyH21tJs7naza319B15KRK0NWOHuhbGhFmF8T0696tg+2c8XQ==",
+      "peer": true,
+      "dependencies": {
+        "d3-hierarchy": "^3.1.2",
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-label": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/vega-label/-/vega-label-1.2.1.tgz",
+      "integrity": "sha512-n/ackJ5lc0Xs9PInCaGumYn2awomPjJ87EMVT47xNgk2bHmJoZV1Ve/1PUM6Eh/KauY211wPMrNp/9Im+7Ripg==",
+      "peer": true,
+      "dependencies": {
+        "vega-canvas": "^1.2.6",
+        "vega-dataflow": "^5.7.3",
+        "vega-scenegraph": "^4.9.2",
+        "vega-util": "^1.15.2"
+      }
+    },
+    "node_modules/vega-lite": {
+      "version": "5.9.0",
+      "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-5.9.0.tgz",
+      "integrity": "sha512-VA3XDlF6nd/t46KDMfq8eNKOJKy9gpJuM+6CIl3jbWqS97jWXRWXp8DpUyDmbV+iq8n4hqNTaoPqDP/e03kifw==",
+      "dependencies": {
+        "@types/clone": "~2.1.1",
+        "clone": "~2.1.2",
+        "fast-deep-equal": "~3.1.3",
+        "fast-json-stable-stringify": "~2.1.0",
+        "json-stringify-pretty-compact": "~3.0.0",
+        "tslib": "~2.5.0",
+        "vega-event-selector": "~3.0.1",
+        "vega-expression": "~5.1.0",
+        "vega-util": "~1.17.2",
+        "yargs": "~17.7.1"
+      },
+      "bin": {
+        "vl2pdf": "bin/vl2pdf",
+        "vl2png": "bin/vl2png",
+        "vl2svg": "bin/vl2svg",
+        "vl2vg": "bin/vl2vg"
+      },
+      "engines": {
+        "node": ">=16"
+      },
+      "peerDependencies": {
+        "vega": "^5.24.0"
+      }
+    },
+    "node_modules/vega-lite/node_modules/cliui": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.1",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vega-lite/node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/vega-lite/node_modules/y18n": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/vega-lite/node_modules/yargs": {
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+      "dependencies": {
+        "cliui": "^8.0.1",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.3",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^21.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vega-lite/node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vega-loader": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-4.5.1.tgz",
+      "integrity": "sha512-qy5x32SaT0YkEujQM2yKqvLGV9XWQ2aEDSugBFTdYzu/1u4bxdUSRDREOlrJ9Km3RWIOgFiCkobPmFxo47SKuA==",
+      "peer": true,
+      "dependencies": {
+        "d3-dsv": "^3.0.1",
+        "node-fetch": "^2.6.7",
+        "topojson-client": "^3.1.0",
+        "vega-format": "^1.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-parser": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-6.2.0.tgz",
+      "integrity": "sha512-as+QnX8Qxe9q51L1C2sVBd+YYYctP848+zEvkBT2jlI2g30aZ6Uv7sKsq7QTL6DUbhXQKR0XQtzlanckSFdaOQ==",
+      "peer": true,
+      "dependencies": {
+        "vega-dataflow": "^5.7.5",
+        "vega-event-selector": "^3.0.1",
+        "vega-functions": "^5.13.1",
+        "vega-scale": "^7.3.0",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-projection": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-1.6.0.tgz",
+      "integrity": "sha512-LGUaO/kpOEYuTlul+x+lBzyuL9qmMwP1yShdUWYLW+zXoeyGbs5OZW+NbPPwLYqJr5lpXDr/vGztFuA/6g2xvQ==",
+      "peer": true,
+      "dependencies": {
+        "d3-geo": "^3.1.0",
+        "d3-geo-projection": "^4.0.0",
+        "vega-scale": "^7.3.0"
+      }
+    },
+    "node_modules/vega-regression": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-1.2.0.tgz",
+      "integrity": "sha512-6TZoPlhV/280VbxACjRKqlE0Nv48z5g4CSNf1FmGGTWS1rQtElPTranSoVW4d7ET5eVQ6f9QLxNAiALptvEq+g==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "vega-dataflow": "^5.7.3",
+        "vega-statistics": "^1.9.0",
+        "vega-util": "^1.15.2"
+      }
+    },
+    "node_modules/vega-runtime": {
+      "version": "6.1.4",
+      "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-6.1.4.tgz",
+      "integrity": "sha512-0dDYXyFLQcxPQ2OQU0WuBVYLRZnm+/CwVu6i6N4idS7R9VXIX5581EkCh3pZ20pQ/+oaA7oJ0pR9rJgJ6rukRQ==",
+      "peer": true,
+      "dependencies": {
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-scale": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-7.3.0.tgz",
+      "integrity": "sha512-pMOAI2h+e1z7lsqKG+gMfR6NKN2sTcyjZbdJwntooW0uFHwjLGjMSY7kSd3nSEquF0HQ8qF7zR6gs1eRwlGimw==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "d3-interpolate": "^3.0.1",
+        "d3-scale": "^4.0.2",
+        "vega-time": "^2.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-scenegraph": {
+      "version": "4.10.2",
+      "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-4.10.2.tgz",
+      "integrity": "sha512-R8m6voDZO5+etwNMcXf45afVM3XAtokMqxuDyddRl9l1YqSJfS+3u8hpolJ50c2q6ZN20BQiJwKT1o0bB7vKkA==",
+      "peer": true,
+      "dependencies": {
+        "d3-path": "^3.1.0",
+        "d3-shape": "^3.2.0",
+        "vega-canvas": "^1.2.7",
+        "vega-loader": "^4.5.1",
+        "vega-scale": "^7.3.0",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-selections": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-5.4.1.tgz",
+      "integrity": "sha512-EtYc4DvA+wXqBg9tq+kDomSoVUPCmQfS7hUxy2qskXEed79YTimt3Hcl1e1fW226I4AVDBEqTTKebmKMzbSgAA==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "3.2.2",
+        "vega-expression": "^5.0.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-selections/node_modules/d3-array": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.2.tgz",
+      "integrity": "sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ==",
+      "peer": true,
+      "dependencies": {
+        "internmap": "1 - 2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/vega-statistics": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-1.9.0.tgz",
+      "integrity": "sha512-GAqS7mkatpXcMCQKWtFu1eMUKLUymjInU0O8kXshWaQrVWjPIO2lllZ1VNhdgE0qGj4oOIRRS11kzuijLshGXQ==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2"
+      }
+    },
+    "node_modules/vega-time": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-2.1.1.tgz",
+      "integrity": "sha512-z1qbgyX0Af2kQSGFbApwBbX2meenGvsoX8Nga8uyWN8VIbiySo/xqizz1KrP6NbB6R+x5egKmkjdnyNThPeEWA==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "d3-time": "^3.1.0",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-transforms": {
+      "version": "4.10.2",
+      "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-4.10.2.tgz",
+      "integrity": "sha512-sJELfEuYQ238PRG+GOqQch8D69RYnJevYSGLsRGQD2LxNz3j+GlUX6Pid+gUEH5HJy22Q5L0vsTl2ZNhIr4teQ==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "vega-dataflow": "^5.7.5",
+        "vega-statistics": "^1.8.1",
+        "vega-time": "^2.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-typings": {
+      "version": "0.24.1",
+      "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-0.24.1.tgz",
+      "integrity": "sha512-WNw6tDxwMsynQ9osJb3RZi3g8GZruxVgXfe8N7nbqvNOgDQkUuVjqTZiwGg5kqjmLqx09lRRlskgp/ov7lEGeg==",
+      "peer": true,
+      "dependencies": {
+        "@types/geojson": "7946.0.4",
+        "vega-event-selector": "^3.0.1",
+        "vega-expression": "^5.0.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-util": {
+      "version": "1.17.2",
+      "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.2.tgz",
+      "integrity": "sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw=="
+    },
+    "node_modules/vega-view": {
+      "version": "5.11.1",
+      "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-5.11.1.tgz",
+      "integrity": "sha512-RoWxuoEMI7xVQJhPqNeLEHCezudsf3QkVMhH5tCovBqwBADQGqq9iWyax3ZzdyX1+P3eBgm7cnLvpqtN2hU8kA==",
+      "peer": true,
+      "dependencies": {
+        "d3-array": "^3.2.2",
+        "d3-timer": "^3.0.1",
+        "vega-dataflow": "^5.7.5",
+        "vega-format": "^1.1.1",
+        "vega-functions": "^5.13.1",
+        "vega-runtime": "^6.1.4",
+        "vega-scenegraph": "^4.10.2",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-view-transforms": {
+      "version": "4.5.9",
+      "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-4.5.9.tgz",
+      "integrity": "sha512-NxEq4ZD4QwWGRrl2yDLnBRXM9FgCI+vvYb3ZC2+nVDtkUxOlEIKZsMMw31op5GZpfClWLbjCT3mVvzO2xaTF+g==",
+      "peer": true,
+      "dependencies": {
+        "vega-dataflow": "^5.7.5",
+        "vega-scenegraph": "^4.10.2",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-voronoi": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-4.2.1.tgz",
+      "integrity": "sha512-zzi+fxU/SBad4irdLLsG3yhZgXWZezraGYVQfZFWe8kl7W/EHUk+Eqk/eetn4bDeJ6ltQskX+UXH3OP5Vh0Q0Q==",
+      "peer": true,
+      "dependencies": {
+        "d3-delaunay": "^6.0.2",
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "node_modules/vega-wordcloud": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-4.1.4.tgz",
+      "integrity": "sha512-oeZLlnjiusLAU5vhk0IIdT5QEiJE0x6cYoGNq1th+EbwgQp153t4r026fcib9oq15glHFOzf81a8hHXHSJm1Jw==",
+      "peer": true,
+      "dependencies": {
+        "vega-canvas": "^1.2.7",
+        "vega-dataflow": "^5.7.5",
+        "vega-scale": "^7.3.0",
+        "vega-statistics": "^1.8.1",
+        "vega-util": "^1.17.1"
+      }
+    },
     "node_modules/vlq": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz",
@@ -10237,8 +11004,7 @@
     "node_modules/webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
-      "dev": true
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
     },
     "node_modules/whatwg-encoding": {
       "version": "1.0.5",
@@ -10271,7 +11037,6 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
       "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
-      "dev": true,
       "dependencies": {
         "tr46": "~0.0.3",
         "webidl-conversions": "^3.0.0"
@@ -11706,6 +12471,11 @@
         "@types/har-format": "*"
       }
     },
+    "@types/clone": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@types/clone/-/clone-2.1.1.tgz",
+      "integrity": "sha512-BZIU34bSYye0j/BFcPraiDZ5ka6MJADjcDVELGf7glr9K+iE8NYVjFslJFVWzskSxkLLyCrSPScE82/UUoBSvg=="
+    },
     "@types/color-convert": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-1.9.0.tgz",
@@ -11738,6 +12508,12 @@
       "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.29.tgz",
       "integrity": "sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ=="
     },
+    "@types/geojson": {
+      "version": "7946.0.4",
+      "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.4.tgz",
+      "integrity": "sha512-MHmwBtCb7OCv1DSivz2UNJXPGU/1btAWRKlqJ2saEhVJkpkvqHMMaOpKg0v4sAbDWSQekHGvPVMM8nQ+Jen03Q==",
+      "peer": true
+    },
     "@types/graceful-fs": {
       "version": "4.1.6",
       "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz",
@@ -12161,14 +12937,12 @@
     "ansi-regex": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
     },
     "ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
       "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-      "dev": true,
       "requires": {
         "color-convert": "^2.0.1"
       }
@@ -12711,6 +13485,11 @@
         "wrap-ansi": "^6.2.0"
       }
     },
+    "clone": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+      "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="
+    },
     "co": {
       "version": "4.6.0",
       "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -12761,6 +13540,12 @@
         "delayed-stream": "~1.0.0"
       }
     },
+    "commander": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+      "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+      "peer": true
+    },
     "commondir": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
@@ -12860,6 +13645,157 @@
     "custom_utils": {
       "version": "file:src/base/utils"
     },
+    "d3-array": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.3.tgz",
+      "integrity": "sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==",
+      "peer": true,
+      "requires": {
+        "internmap": "1 - 2"
+      }
+    },
+    "d3-color": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+      "peer": true
+    },
+    "d3-delaunay": {
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+      "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+      "peer": true,
+      "requires": {
+        "delaunator": "5"
+      }
+    },
+    "d3-dispatch": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
+      "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==",
+      "peer": true
+    },
+    "d3-dsv": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
+      "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
+      "peer": true,
+      "requires": {
+        "commander": "7",
+        "iconv-lite": "0.6",
+        "rw": "1"
+      }
+    },
+    "d3-force": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
+      "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==",
+      "peer": true,
+      "requires": {
+        "d3-dispatch": "1 - 3",
+        "d3-quadtree": "1 - 3",
+        "d3-timer": "1 - 3"
+      }
+    },
+    "d3-format": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
+      "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
+      "peer": true
+    },
+    "d3-geo": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.0.tgz",
+      "integrity": "sha512-JEo5HxXDdDYXCaWdwLRt79y7giK8SbhZJbFWXqbRTolCHFI5jRqteLzCsq51NKbUoX0PjBVSohxrx+NoOUujYA==",
+      "peer": true,
+      "requires": {
+        "d3-array": "2.5.0 - 3"
+      }
+    },
+    "d3-geo-projection": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/d3-geo-projection/-/d3-geo-projection-4.0.0.tgz",
+      "integrity": "sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==",
+      "peer": true,
+      "requires": {
+        "commander": "7",
+        "d3-array": "1 - 3",
+        "d3-geo": "1.12.0 - 3"
+      }
+    },
+    "d3-hierarchy": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
+      "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
+      "peer": true
+    },
+    "d3-interpolate": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+      "peer": true,
+      "requires": {
+        "d3-color": "1 - 3"
+      }
+    },
+    "d3-path": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+      "peer": true
+    },
+    "d3-quadtree": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
+      "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==",
+      "peer": true
+    },
+    "d3-scale": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+      "peer": true,
+      "requires": {
+        "d3-array": "2.10.0 - 3",
+        "d3-format": "1 - 3",
+        "d3-interpolate": "1.2.0 - 3",
+        "d3-time": "2.1.1 - 3",
+        "d3-time-format": "2 - 4"
+      }
+    },
+    "d3-shape": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+      "peer": true,
+      "requires": {
+        "d3-path": "^3.1.0"
+      }
+    },
+    "d3-time": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+      "peer": true,
+      "requires": {
+        "d3-array": "2 - 3"
+      }
+    },
+    "d3-time-format": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+      "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+      "peer": true,
+      "requires": {
+        "d3-time": "1 - 3"
+      }
+    },
+    "d3-timer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+      "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+      "peer": true
+    },
     "data-urls": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz",
@@ -12966,6 +13902,15 @@
         "isobject": "^3.0.1"
       }
     },
+    "delaunator": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.0.tgz",
+      "integrity": "sha512-AyLvtyJdbv/U1GkiS6gUUzclRoAY4Gs75qkMygJJhU75LW4DNuSF2RMzpxs9jw9Oz1BobHjTdkG3zdP55VxAqw==",
+      "peer": true,
+      "requires": {
+        "robust-predicates": "^3.0.0"
+      }
+    },
     "delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -13057,14 +14002,12 @@
     "emoji-regex": {
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "dev": true
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
     },
     "encoding": {
       "version": "0.1.13",
       "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
       "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
-      "dev": true,
       "optional": true,
       "requires": {
         "iconv-lite": "^0.6.2"
@@ -13252,8 +14195,7 @@
     "escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
-      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
-      "dev": true
+      "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
     },
     "escape-string-regexp": {
       "version": "4.0.0",
@@ -13707,8 +14649,7 @@
     "fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
     "fast-glob": {
       "version": "3.2.12",
@@ -13737,8 +14678,7 @@
     "fast-json-stable-stringify": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
-      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
-      "dev": true
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
     },
     "fast-levenshtein": {
       "version": "2.0.6",
@@ -13890,8 +14830,7 @@
     "get-caller-file": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
-      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "dev": true
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
     },
     "get-intrinsic": {
       "version": "1.2.0",
@@ -14221,8 +15160,6 @@
       "version": "0.6.3",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
       "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
-      "dev": true,
-      "optional": true,
       "requires": {
         "safer-buffer": ">= 2.1.2 < 3.0.0"
       }
@@ -14296,6 +15233,12 @@
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
     },
+    "internmap": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+      "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+      "peer": true
+    },
     "ip": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
@@ -14409,8 +15352,7 @@
     "is-fullwidth-code-point": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-      "dev": true
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
     },
     "is-generator-fn": {
       "version": "2.1.0",
@@ -15246,6 +16188,11 @@
       "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
       "dev": true
     },
+    "json-stringify-pretty-compact": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz",
+      "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA=="
+    },
     "json5": {
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -15698,7 +16645,6 @@
       "version": "2.6.7",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
       "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
-      "dev": true,
       "requires": {
         "whatwg-url": "^5.0.0"
       }
@@ -16714,8 +17660,7 @@
     "require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
-      "dev": true
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
     },
     "require-main-filename": {
       "version": "2.0.0",
@@ -16796,6 +17741,12 @@
         "glob": "^7.1.3"
       }
     },
+    "robust-predicates": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.1.tgz",
+      "integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==",
+      "peer": true
+    },
     "rollup": {
       "version": "2.79.1",
       "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
@@ -16868,6 +17819,12 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "rw": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
+      "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
+      "peer": true
+    },
     "safe-buffer": {
       "version": "5.2.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -16886,8 +17843,7 @@
     "safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-      "dev": true
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
     "sane": {
       "version": "4.1.0",
@@ -17764,7 +18720,6 @@
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "dev": true,
       "requires": {
         "emoji-regex": "^8.0.0",
         "is-fullwidth-code-point": "^3.0.0",
@@ -17775,7 +18730,6 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
       "requires": {
         "ansi-regex": "^5.0.1"
       }
@@ -17997,11 +18951,27 @@
         "is-number": "^7.0.0"
       }
     },
+    "topojson-client": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz",
+      "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==",
+      "peer": true,
+      "requires": {
+        "commander": "2"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.20.3",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+          "peer": true
+        }
+      }
+    },
     "tr46": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
-      "dev": true
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
     },
     "trim-newlines": {
       "version": "3.0.1",
@@ -18018,8 +18988,7 @@
     "tslib": {
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
-      "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
-      "dev": true
+      "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
     },
     "type-check": {
       "version": "0.4.0",
@@ -18244,6 +19213,457 @@
         "spdx-expression-parse": "^3.0.0"
       }
     },
+    "vega": {
+      "version": "5.25.0",
+      "resolved": "https://registry.npmjs.org/vega/-/vega-5.25.0.tgz",
+      "integrity": "sha512-lr+uj0mhYlSN3JOKbMNp1RzZBenWp9DxJ7kR3lha58AFNCzzds7pmFa7yXPbtbaGhB7Buh/t6n+Bzk3Y0VnF5g==",
+      "peer": true,
+      "requires": {
+        "vega-crossfilter": "~4.1.1",
+        "vega-dataflow": "~5.7.5",
+        "vega-encode": "~4.9.2",
+        "vega-event-selector": "~3.0.1",
+        "vega-expression": "~5.1.0",
+        "vega-force": "~4.2.0",
+        "vega-format": "~1.1.1",
+        "vega-functions": "~5.13.2",
+        "vega-geo": "~4.4.1",
+        "vega-hierarchy": "~4.1.1",
+        "vega-label": "~1.2.1",
+        "vega-loader": "~4.5.1",
+        "vega-parser": "~6.2.0",
+        "vega-projection": "~1.6.0",
+        "vega-regression": "~1.2.0",
+        "vega-runtime": "~6.1.4",
+        "vega-scale": "~7.3.0",
+        "vega-scenegraph": "~4.10.2",
+        "vega-statistics": "~1.9.0",
+        "vega-time": "~2.1.1",
+        "vega-transforms": "~4.10.2",
+        "vega-typings": "~0.24.0",
+        "vega-util": "~1.17.2",
+        "vega-view": "~5.11.1",
+        "vega-view-transforms": "~4.5.9",
+        "vega-voronoi": "~4.2.1",
+        "vega-wordcloud": "~4.1.4"
+      }
+    },
+    "vega-canvas": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/vega-canvas/-/vega-canvas-1.2.7.tgz",
+      "integrity": "sha512-OkJ9CACVcN9R5Pi9uF6MZBF06pO6qFpDYHWSKBJsdHP5o724KrsgR6UvbnXFH82FdsiTOff/HqjuaG8C7FL+9Q==",
+      "peer": true
+    },
+    "vega-crossfilter": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/vega-crossfilter/-/vega-crossfilter-4.1.1.tgz",
+      "integrity": "sha512-yesvlMcwRwxrtAd9IYjuxWJJuAMI0sl7JvAFfYtuDkkGDtqfLXUcCzHIATqW6igVIE7tWwGxnbfvQLhLNgK44Q==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-dataflow": {
+      "version": "5.7.5",
+      "resolved": "https://registry.npmjs.org/vega-dataflow/-/vega-dataflow-5.7.5.tgz",
+      "integrity": "sha512-EdsIl6gouH67+8B0f22Owr2tKDiMPNNR8lEvJDcxmFw02nXd8juimclpLvjPQriqn6ta+3Dn5txqfD117H04YA==",
+      "peer": true,
+      "requires": {
+        "vega-format": "^1.1.1",
+        "vega-loader": "^4.5.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-encode": {
+      "version": "4.9.2",
+      "resolved": "https://registry.npmjs.org/vega-encode/-/vega-encode-4.9.2.tgz",
+      "integrity": "sha512-c3J0LYkgYeXQxwnYkEzL15cCFBYPRaYUon8O2SZ6O4PhH4dfFTXBzSyT8+gh8AhBd572l2yGDfxpEYA6pOqdjg==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "d3-interpolate": "^3.0.1",
+        "vega-dataflow": "^5.7.5",
+        "vega-scale": "^7.3.0",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-event-selector": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/vega-event-selector/-/vega-event-selector-3.0.1.tgz",
+      "integrity": "sha512-K5zd7s5tjr1LiOOkjGpcVls8GsH/f2CWCrWcpKy74gTCp+llCdwz0Enqo013ZlGaRNjfgD/o1caJRt3GSaec4A=="
+    },
+    "vega-expression": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/vega-expression/-/vega-expression-5.1.0.tgz",
+      "integrity": "sha512-u8Rzja/cn2PEUkhQN3zUj3REwNewTA92ExrcASNKUJPCciMkHJEjESwFYuI6DWMCq4hQElQ92iosOAtwzsSTqA==",
+      "requires": {
+        "@types/estree": "^1.0.0",
+        "vega-util": "^1.17.1"
+      },
+      "dependencies": {
+        "@types/estree": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz",
+          "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA=="
+        }
+      }
+    },
+    "vega-force": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/vega-force/-/vega-force-4.2.0.tgz",
+      "integrity": "sha512-aE2TlP264HXM1r3fl58AvZdKUWBNOGkIvn4EWyqeJdgO2vz46zSU7x7TzPG4ZLuo44cDRU5Ng3I1eQk23Asz6A==",
+      "peer": true,
+      "requires": {
+        "d3-force": "^3.0.0",
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-format": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/vega-format/-/vega-format-1.1.1.tgz",
+      "integrity": "sha512-Rll7YgpYbsgaAa54AmtEWrxaJqgOh5fXlvM2wewO4trb9vwM53KBv4Q/uBWCLK3LLGeBXIF6gjDt2LFuJAUtkQ==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "d3-format": "^3.1.0",
+        "d3-time-format": "^4.1.0",
+        "vega-time": "^2.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-functions": {
+      "version": "5.13.2",
+      "resolved": "https://registry.npmjs.org/vega-functions/-/vega-functions-5.13.2.tgz",
+      "integrity": "sha512-YE1Xl3Qi28kw3vdXVYgKFMo20ttd3+SdKth1jUNtBDGGdrOpvPxxFhZkVqX+7FhJ5/1UkDoAYs/cZY0nRKiYgA==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "d3-color": "^3.1.0",
+        "d3-geo": "^3.1.0",
+        "vega-dataflow": "^5.7.5",
+        "vega-expression": "^5.1.0",
+        "vega-scale": "^7.3.0",
+        "vega-scenegraph": "^4.10.2",
+        "vega-selections": "^5.4.1",
+        "vega-statistics": "^1.8.1",
+        "vega-time": "^2.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-geo": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/vega-geo/-/vega-geo-4.4.1.tgz",
+      "integrity": "sha512-s4WeZAL5M3ZUV27/eqSD3v0FyJz3PlP31XNSLFy4AJXHxHUeXT3qLiDHoVQnW5Om+uBCPDtTT1ROx1smGIf2aA==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "d3-color": "^3.1.0",
+        "d3-geo": "^3.1.0",
+        "vega-canvas": "^1.2.7",
+        "vega-dataflow": "^5.7.5",
+        "vega-projection": "^1.6.0",
+        "vega-statistics": "^1.8.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-hierarchy": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/vega-hierarchy/-/vega-hierarchy-4.1.1.tgz",
+      "integrity": "sha512-h5mbrDtPKHBBQ9TYbvEb/bCqmGTlUX97+4CENkyH21tJs7naza319B15KRK0NWOHuhbGhFmF8T0696tg+2c8XQ==",
+      "peer": true,
+      "requires": {
+        "d3-hierarchy": "^3.1.2",
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-label": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/vega-label/-/vega-label-1.2.1.tgz",
+      "integrity": "sha512-n/ackJ5lc0Xs9PInCaGumYn2awomPjJ87EMVT47xNgk2bHmJoZV1Ve/1PUM6Eh/KauY211wPMrNp/9Im+7Ripg==",
+      "peer": true,
+      "requires": {
+        "vega-canvas": "^1.2.6",
+        "vega-dataflow": "^5.7.3",
+        "vega-scenegraph": "^4.9.2",
+        "vega-util": "^1.15.2"
+      }
+    },
+    "vega-lite": {
+      "version": "5.9.0",
+      "resolved": "https://registry.npmjs.org/vega-lite/-/vega-lite-5.9.0.tgz",
+      "integrity": "sha512-VA3XDlF6nd/t46KDMfq8eNKOJKy9gpJuM+6CIl3jbWqS97jWXRWXp8DpUyDmbV+iq8n4hqNTaoPqDP/e03kifw==",
+      "requires": {
+        "@types/clone": "~2.1.1",
+        "clone": "~2.1.2",
+        "fast-deep-equal": "~3.1.3",
+        "fast-json-stable-stringify": "~2.1.0",
+        "json-stringify-pretty-compact": "~3.0.0",
+        "tslib": "~2.5.0",
+        "vega-event-selector": "~3.0.1",
+        "vega-expression": "~5.1.0",
+        "vega-util": "~1.17.2",
+        "yargs": "~17.7.1"
+      },
+      "dependencies": {
+        "cliui": {
+          "version": "8.0.1",
+          "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+          "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+          "requires": {
+            "string-width": "^4.2.0",
+            "strip-ansi": "^6.0.1",
+            "wrap-ansi": "^7.0.0"
+          }
+        },
+        "wrap-ansi": {
+          "version": "7.0.0",
+          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+          "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+          "requires": {
+            "ansi-styles": "^4.0.0",
+            "string-width": "^4.1.0",
+            "strip-ansi": "^6.0.0"
+          }
+        },
+        "y18n": {
+          "version": "5.0.8",
+          "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+          "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
+        },
+        "yargs": {
+          "version": "17.7.2",
+          "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+          "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+          "requires": {
+            "cliui": "^8.0.1",
+            "escalade": "^3.1.1",
+            "get-caller-file": "^2.0.5",
+            "require-directory": "^2.1.1",
+            "string-width": "^4.2.3",
+            "y18n": "^5.0.5",
+            "yargs-parser": "^21.1.1"
+          }
+        },
+        "yargs-parser": {
+          "version": "21.1.1",
+          "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+          "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
+        }
+      }
+    },
+    "vega-loader": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/vega-loader/-/vega-loader-4.5.1.tgz",
+      "integrity": "sha512-qy5x32SaT0YkEujQM2yKqvLGV9XWQ2aEDSugBFTdYzu/1u4bxdUSRDREOlrJ9Km3RWIOgFiCkobPmFxo47SKuA==",
+      "peer": true,
+      "requires": {
+        "d3-dsv": "^3.0.1",
+        "node-fetch": "^2.6.7",
+        "topojson-client": "^3.1.0",
+        "vega-format": "^1.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-parser": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmjs.org/vega-parser/-/vega-parser-6.2.0.tgz",
+      "integrity": "sha512-as+QnX8Qxe9q51L1C2sVBd+YYYctP848+zEvkBT2jlI2g30aZ6Uv7sKsq7QTL6DUbhXQKR0XQtzlanckSFdaOQ==",
+      "peer": true,
+      "requires": {
+        "vega-dataflow": "^5.7.5",
+        "vega-event-selector": "^3.0.1",
+        "vega-functions": "^5.13.1",
+        "vega-scale": "^7.3.0",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-projection": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/vega-projection/-/vega-projection-1.6.0.tgz",
+      "integrity": "sha512-LGUaO/kpOEYuTlul+x+lBzyuL9qmMwP1yShdUWYLW+zXoeyGbs5OZW+NbPPwLYqJr5lpXDr/vGztFuA/6g2xvQ==",
+      "peer": true,
+      "requires": {
+        "d3-geo": "^3.1.0",
+        "d3-geo-projection": "^4.0.0",
+        "vega-scale": "^7.3.0"
+      }
+    },
+    "vega-regression": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/vega-regression/-/vega-regression-1.2.0.tgz",
+      "integrity": "sha512-6TZoPlhV/280VbxACjRKqlE0Nv48z5g4CSNf1FmGGTWS1rQtElPTranSoVW4d7ET5eVQ6f9QLxNAiALptvEq+g==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "vega-dataflow": "^5.7.3",
+        "vega-statistics": "^1.9.0",
+        "vega-util": "^1.15.2"
+      }
+    },
+    "vega-runtime": {
+      "version": "6.1.4",
+      "resolved": "https://registry.npmjs.org/vega-runtime/-/vega-runtime-6.1.4.tgz",
+      "integrity": "sha512-0dDYXyFLQcxPQ2OQU0WuBVYLRZnm+/CwVu6i6N4idS7R9VXIX5581EkCh3pZ20pQ/+oaA7oJ0pR9rJgJ6rukRQ==",
+      "peer": true,
+      "requires": {
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-scale": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/vega-scale/-/vega-scale-7.3.0.tgz",
+      "integrity": "sha512-pMOAI2h+e1z7lsqKG+gMfR6NKN2sTcyjZbdJwntooW0uFHwjLGjMSY7kSd3nSEquF0HQ8qF7zR6gs1eRwlGimw==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "d3-interpolate": "^3.0.1",
+        "d3-scale": "^4.0.2",
+        "vega-time": "^2.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-scenegraph": {
+      "version": "4.10.2",
+      "resolved": "https://registry.npmjs.org/vega-scenegraph/-/vega-scenegraph-4.10.2.tgz",
+      "integrity": "sha512-R8m6voDZO5+etwNMcXf45afVM3XAtokMqxuDyddRl9l1YqSJfS+3u8hpolJ50c2q6ZN20BQiJwKT1o0bB7vKkA==",
+      "peer": true,
+      "requires": {
+        "d3-path": "^3.1.0",
+        "d3-shape": "^3.2.0",
+        "vega-canvas": "^1.2.7",
+        "vega-loader": "^4.5.1",
+        "vega-scale": "^7.3.0",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-selections": {
+      "version": "5.4.1",
+      "resolved": "https://registry.npmjs.org/vega-selections/-/vega-selections-5.4.1.tgz",
+      "integrity": "sha512-EtYc4DvA+wXqBg9tq+kDomSoVUPCmQfS7hUxy2qskXEed79YTimt3Hcl1e1fW226I4AVDBEqTTKebmKMzbSgAA==",
+      "peer": true,
+      "requires": {
+        "d3-array": "3.2.2",
+        "vega-expression": "^5.0.1",
+        "vega-util": "^1.17.1"
+      },
+      "dependencies": {
+        "d3-array": {
+          "version": "3.2.2",
+          "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.2.tgz",
+          "integrity": "sha512-yEEyEAbDrF8C6Ob2myOBLjwBLck1Z89jMGFee0oPsn95GqjerpaOA4ch+vc2l0FNFFwMD5N7OCSEN5eAlsUbgQ==",
+          "peer": true,
+          "requires": {
+            "internmap": "1 - 2"
+          }
+        }
+      }
+    },
+    "vega-statistics": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/vega-statistics/-/vega-statistics-1.9.0.tgz",
+      "integrity": "sha512-GAqS7mkatpXcMCQKWtFu1eMUKLUymjInU0O8kXshWaQrVWjPIO2lllZ1VNhdgE0qGj4oOIRRS11kzuijLshGXQ==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2"
+      }
+    },
+    "vega-time": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/vega-time/-/vega-time-2.1.1.tgz",
+      "integrity": "sha512-z1qbgyX0Af2kQSGFbApwBbX2meenGvsoX8Nga8uyWN8VIbiySo/xqizz1KrP6NbB6R+x5egKmkjdnyNThPeEWA==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "d3-time": "^3.1.0",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-transforms": {
+      "version": "4.10.2",
+      "resolved": "https://registry.npmjs.org/vega-transforms/-/vega-transforms-4.10.2.tgz",
+      "integrity": "sha512-sJELfEuYQ238PRG+GOqQch8D69RYnJevYSGLsRGQD2LxNz3j+GlUX6Pid+gUEH5HJy22Q5L0vsTl2ZNhIr4teQ==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "vega-dataflow": "^5.7.5",
+        "vega-statistics": "^1.8.1",
+        "vega-time": "^2.1.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-typings": {
+      "version": "0.24.1",
+      "resolved": "https://registry.npmjs.org/vega-typings/-/vega-typings-0.24.1.tgz",
+      "integrity": "sha512-WNw6tDxwMsynQ9osJb3RZi3g8GZruxVgXfe8N7nbqvNOgDQkUuVjqTZiwGg5kqjmLqx09lRRlskgp/ov7lEGeg==",
+      "peer": true,
+      "requires": {
+        "@types/geojson": "7946.0.4",
+        "vega-event-selector": "^3.0.1",
+        "vega-expression": "^5.0.1",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-util": {
+      "version": "1.17.2",
+      "resolved": "https://registry.npmjs.org/vega-util/-/vega-util-1.17.2.tgz",
+      "integrity": "sha512-omNmGiZBdjm/jnHjZlywyYqafscDdHaELHx1q96n5UOz/FlO9JO99P4B3jZg391EFG8dqhWjQilSf2JH6F1mIw=="
+    },
+    "vega-view": {
+      "version": "5.11.1",
+      "resolved": "https://registry.npmjs.org/vega-view/-/vega-view-5.11.1.tgz",
+      "integrity": "sha512-RoWxuoEMI7xVQJhPqNeLEHCezudsf3QkVMhH5tCovBqwBADQGqq9iWyax3ZzdyX1+P3eBgm7cnLvpqtN2hU8kA==",
+      "peer": true,
+      "requires": {
+        "d3-array": "^3.2.2",
+        "d3-timer": "^3.0.1",
+        "vega-dataflow": "^5.7.5",
+        "vega-format": "^1.1.1",
+        "vega-functions": "^5.13.1",
+        "vega-runtime": "^6.1.4",
+        "vega-scenegraph": "^4.10.2",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-view-transforms": {
+      "version": "4.5.9",
+      "resolved": "https://registry.npmjs.org/vega-view-transforms/-/vega-view-transforms-4.5.9.tgz",
+      "integrity": "sha512-NxEq4ZD4QwWGRrl2yDLnBRXM9FgCI+vvYb3ZC2+nVDtkUxOlEIKZsMMw31op5GZpfClWLbjCT3mVvzO2xaTF+g==",
+      "peer": true,
+      "requires": {
+        "vega-dataflow": "^5.7.5",
+        "vega-scenegraph": "^4.10.2",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-voronoi": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/vega-voronoi/-/vega-voronoi-4.2.1.tgz",
+      "integrity": "sha512-zzi+fxU/SBad4irdLLsG3yhZgXWZezraGYVQfZFWe8kl7W/EHUk+Eqk/eetn4bDeJ6ltQskX+UXH3OP5Vh0Q0Q==",
+      "peer": true,
+      "requires": {
+        "d3-delaunay": "^6.0.2",
+        "vega-dataflow": "^5.7.5",
+        "vega-util": "^1.17.1"
+      }
+    },
+    "vega-wordcloud": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/vega-wordcloud/-/vega-wordcloud-4.1.4.tgz",
+      "integrity": "sha512-oeZLlnjiusLAU5vhk0IIdT5QEiJE0x6cYoGNq1th+EbwgQp153t4r026fcib9oq15glHFOzf81a8hHXHSJm1Jw==",
+      "peer": true,
+      "requires": {
+        "vega-canvas": "^1.2.7",
+        "vega-dataflow": "^5.7.5",
+        "vega-scale": "^7.3.0",
+        "vega-statistics": "^1.8.1",
+        "vega-util": "^1.17.1"
+      }
+    },
     "vlq": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz",
@@ -18280,8 +19700,7 @@
     "webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
-      "dev": true
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
     },
     "whatwg-encoding": {
       "version": "1.0.5",
@@ -18313,7 +19732,6 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
       "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
-      "dev": true,
       "requires": {
         "tr46": "~0.0.3",
         "webidl-conversions": "^3.0.0"
diff --git a/ui/package.json b/ui/package.json
index 0f4d69b..1257ab7 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -30,7 +30,8 @@
     "pako": "^1.0.11",
     "protobufjs": "^6.9.0",
     "util": "^0.12.3",
-    "uuid": "^9.0.0"
+    "uuid": "^9.0.0",
+    "vega-lite": "^5.9.0"
   },
   "devDependencies": {
     "@rollup/plugin-commonjs": "^24.0.1",
diff --git a/ui/release/channels.json b/ui/release/channels.json
index fb05de4..09f14f4 100644
--- a/ui/release/channels.json
+++ b/ui/release/channels.json
@@ -2,11 +2,11 @@
   "channels": [
     {
       "name": "stable",
-      "rev": "1f0f1aa6babb0b3ff273c5102aa677b2604e9a4e"
+      "rev": "1b8de44522f33d371def110325bf31b847c1f5c4"
     },
     {
       "name": "canary",
-      "rev": "056cc57893b57b82ed411256c2bd4091641f55fb"
+      "rev": "a34bc46479e2e659532f7e208c6eba5462c26bae"
     },
     {
       "name": "autopush",
diff --git a/ui/src/assets/common.scss b/ui/src/assets/common.scss
index 79ba007..0435422 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -42,17 +42,14 @@
 }
 
 @mixin track_shell_title() {
-  // line-height is deliberately 1px larger than font-size. Roboto seems to
-  // overflow on the bottom on "g"s otherwise.
   font-size: 14px;
-  line-height: 15px;
   max-height: 30px;
   overflow: hidden;
   text-align: left;
   overflow-wrap: break-word;
   font-family: "Roboto Condensed", sans-serif;
   font-weight: 300;
-  letter-spacing: -0.5px;
+  line-break: anywhere;
 }
 
 * {
@@ -388,6 +385,7 @@
     width: var(--track-shell-width);
     background: #fff;
     border-right: 1px solid #c7d0db;
+    overflow: hidden;
 
     &.drag {
       background-color: #eee;
@@ -462,7 +460,7 @@
 .scrolling-panel-container {
   position: relative;
   overflow-x: hidden;
-  overflow-y: auto;
+  overflow-y: scroll; // Always show vertical scrollbar
   flex: 1 1 auto;
   will-change: transform; // Force layer creation.
   display: grid;
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index 9ba93b3..b5c131b 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -293,6 +293,7 @@
 
 .details-table-multicolumn {
   display: flex;
+  user-select: 'text';
 }
 
 .flow-link:hover {
diff --git a/ui/src/assets/perfetto.scss b/ui/src/assets/perfetto.scss
index 7a00576..e4da147 100644
--- a/ui/src/assets/perfetto.scss
+++ b/ui/src/assets/perfetto.scss
@@ -38,3 +38,4 @@
 @import "widgets/spinner";
 @import "widgets/tree";
 @import "widgets/switch";
+@import "widgets/form";
diff --git a/ui/src/assets/record.scss b/ui/src/assets/record.scss
index e4820d8..187499f6 100644
--- a/ui/src/assets/record.scss
+++ b/ui/src/assets/record.scss
@@ -399,6 +399,8 @@
       line-height: 12.5px;
       margin-top: -5px;
       opacity: 0;
+      text-overflow: ellipsis;
+      white-space: nowrap;
     }
 
     &:hover {
diff --git a/ui/src/base/math_utils.ts b/ui/src/assets/widgets/form.scss
similarity index 60%
rename from ui/src/base/math_utils.ts
rename to ui/src/assets/widgets/form.scss
index f1c1816..569df72 100644
--- a/ui/src/base/math_utils.ts
+++ b/ui/src/assets/widgets/form.scss
@@ -12,12 +12,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// Round a number up to the nearest stepsize.
-export function roundUpNearest(val: number, stepsize: number): number {
-  return stepsize * Math.ceil(val / stepsize);
-}
+.pf-form {
+  display: grid;
+  grid-template-columns: auto auto;
+  margin: 6px;
+  row-gap: 8px;
+  column-gap: 8px;
 
-// Round a number down to the nearest stepsize.
-export function roundDownNearest(val: number, stepsize: number): number {
-  return stepsize * Math.floor(val / stepsize);
+  .pf-form-button-bar {
+    grid-column: span 2;
+    margin-top: 6px;
+    display: flex;
+    justify-content: right;
+    flex-direction: row-reverse;
+    align-items: center;
+
+    .pf-button {
+      margin-right: 4px;
+    }
+  }
+
+  .pf-form-label {
+    font-weight: 600;
+  }
 }
diff --git a/ui/src/assets/widgets/menu.scss b/ui/src/assets/widgets/menu.scss
index e161d4c..5bc6a9f 100644
--- a/ui/src/assets/widgets/menu.scss
+++ b/ui/src/assets/widgets/menu.scss
@@ -22,10 +22,10 @@
 
   .pf-menu-item {
     font-family: $pf-font;
-    font-size: inherit;
+    font-size: 14px;
     user-select: none;
     text-align: left;
-    padding: 6px 12px;
+    padding: 5px 10px;
     white-space: nowrap;
     min-width: max-content;
     cursor: pointer;
diff --git a/ui/src/base/bigint_math.ts b/ui/src/base/bigint_math.ts
new file mode 100644
index 0000000..1860afe
--- /dev/null
+++ b/ui/src/base/bigint_math.ts
@@ -0,0 +1,77 @@
+// 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.
+
+export class BigintMath {
+  static INT64_MAX: bigint = (2n ** 63n) - 1n;
+
+  // Returns the smallest integral power of 2 that is not smaller than n.
+  // If n is less than or equal to 0, returns 1.
+  static bitCeil(n: bigint): bigint {
+    let result = 1n;
+    while (result < n) {
+      result <<= 1n;
+    }
+    return result;
+  };
+
+  // Returns the largest integral power of 2 which is not greater than n.
+  // If n is less than or equal to 0, returns 1.
+  static bitFloor(n: bigint): bigint {
+    let result = 1n;
+    while ((result << 1n) <= n) {
+      result <<= 1n;
+    }
+    return result;
+  };
+
+  // Returns the largest integral multiple of step which is not larger than n.
+  // If step is less than or equal to 0, returns n.
+  static quantizeFloor(n: bigint, step: bigint): bigint {
+    step = BigintMath.max(1n, step);
+    return step * (n / step);
+  }
+
+  // Return the integral multiple of step which is closest to n.
+  // If step is less than or equal to 0, returns n.
+  static quantize(n: bigint, step: bigint): bigint {
+    step = BigintMath.max(1n, step);
+    const halfStep = step / 2n;
+    return step * ((n + halfStep) / step);
+  }
+
+  // Return the greater of a and b
+  static max(a: bigint, b: bigint): bigint {
+    return a > b ? a : b;
+  }
+
+  // Return the smaller of a and b
+  static min(a: bigint, b: bigint): bigint {
+    return a < b ? a : b;
+  }
+
+  // Returns the number of 1 bits in n
+  static popcount(n: bigint): number {
+    if (n < 0n) {
+      throw Error(`Can\'t get popcount of negative number ${n}`);
+    }
+    let count = 0;
+    while (n) {
+      if (n & 1n) {
+        ++count;
+      }
+      n >>= 1n;
+    }
+    return count;
+  }
+}
diff --git a/ui/src/base/bigint_math_unittest.ts b/ui/src/base/bigint_math_unittest.ts
new file mode 100644
index 0000000..291eb32
--- /dev/null
+++ b/ui/src/base/bigint_math_unittest.ts
@@ -0,0 +1,141 @@
+// 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 {
+  BigintMath as BIM,
+} from './bigint_math';
+
+describe('BigIntMath.bitCeil', () => {
+  it('rounds powers of 2 to themselves', () => {
+    expect(BIM.bitCeil(1n)).toBe(1n);
+    expect(BIM.bitCeil(2n)).toBe(2n);
+    expect(BIM.bitCeil(4n)).toBe(4n);
+    expect(BIM.bitCeil(4294967296n)).toBe(4294967296n);
+    expect(BIM.bitCeil(2305843009213693952n)).toBe(2305843009213693952n);
+  });
+
+  it('rounds non powers of 2 up to nearest power of 2', () => {
+    expect(BIM.bitCeil(3n)).toBe(4n);
+    expect(BIM.bitCeil(11n)).toBe(16n);
+    expect(BIM.bitCeil(33n)).toBe(64n);
+    expect(BIM.bitCeil(63n)).toBe(64n);
+    expect(BIM.bitCeil(1234567890123456789n)).toBe(2305843009213693952n);
+  });
+
+  it('rounds 0 or negative values up to 1', () => {
+    expect(BIM.bitCeil(0n)).toBe(1n);
+    expect(BIM.bitCeil(-123n)).toBe(1n);
+  });
+});
+
+describe('BigIntMath.bigFloor', () => {
+  it('rounds powers of 2 to themselves', () => {
+    expect(BIM.bitFloor(1n)).toBe(1n);
+    expect(BIM.bitFloor(2n)).toBe(2n);
+    expect(BIM.bitFloor(4n)).toBe(4n);
+    expect(BIM.bitFloor(4294967296n)).toBe(4294967296n);
+    expect(BIM.bitFloor(2305843009213693952n)).toBe(2305843009213693952n);
+  });
+
+  it('rounds non powers of 2 down to nearest power of 2', () => {
+    expect(BIM.bitFloor(3n)).toBe(2n);
+    expect(BIM.bitFloor(11n)).toBe(8n);
+    expect(BIM.bitFloor(33n)).toBe(32n);
+    expect(BIM.bitFloor(63n)).toBe(32n);
+    expect(BIM.bitFloor(1234567890123456789n)).toBe(1152921504606846976n);
+  });
+
+  it('rounds 0 or negative values up to 1', () => {
+    expect(BIM.bitFloor(0n)).toBe(1n);
+    expect(BIM.bitFloor(-123n)).toBe(1n);
+  });
+});
+
+describe('quantize', () => {
+  it('should quantize a number to the nearest multiple of a stepsize', () => {
+    expect(BIM.quantizeFloor(10n, 2n)).toEqual(10n);
+    expect(BIM.quantizeFloor(11n, 2n)).toEqual(10n);
+    expect(BIM.quantizeFloor(12n, 2n)).toEqual(12n);
+    expect(BIM.quantizeFloor(13n, 2n)).toEqual(12n);
+
+    expect(BIM.quantizeFloor(9n, 4n)).toEqual(8n);
+    expect(BIM.quantizeFloor(10n, 4n)).toEqual(8n);
+    expect(BIM.quantizeFloor(11n, 4n)).toEqual(8n);
+    expect(BIM.quantizeFloor(12n, 4n)).toEqual(12n);
+    expect(BIM.quantizeFloor(13n, 4n)).toEqual(12n);
+  });
+
+  it('should return value if stepsize is smaller than 1', () => {
+    expect(BIM.quantizeFloor(123n, 0n)).toEqual(123n);
+    expect(BIM.quantizeFloor(123n, -10n)).toEqual(123n);
+  });
+});
+
+describe('quantizeRound', () => {
+  it('should quantize a number to the nearest multiple of a stepsize', () => {
+    expect(BIM.quantize(0n, 2n)).toEqual(0n);
+    expect(BIM.quantize(1n, 2n)).toEqual(2n);
+    expect(BIM.quantize(2n, 2n)).toEqual(2n);
+    expect(BIM.quantize(3n, 2n)).toEqual(4n);
+    expect(BIM.quantize(4n, 2n)).toEqual(4n);
+
+    expect(BIM.quantize(0n, 3n)).toEqual(0n);
+    expect(BIM.quantize(1n, 3n)).toEqual(0n);
+    expect(BIM.quantize(2n, 3n)).toEqual(3n);
+    expect(BIM.quantize(3n, 3n)).toEqual(3n);
+    expect(BIM.quantize(4n, 3n)).toEqual(3n);
+    expect(BIM.quantize(5n, 3n)).toEqual(6n);
+    expect(BIM.quantize(6n, 3n)).toEqual(6n);
+  });
+
+  it('should return value if stepsize is smaller than 1', () => {
+    expect(BIM.quantize(123n, 0n)).toEqual(123n);
+    expect(BIM.quantize(123n, -10n)).toEqual(123n);
+  });
+});
+
+describe('max', () => {
+  it('should return the greater of two numbers', () => {
+    expect(BIM.max(5n, 8n)).toEqual(8n);
+    expect(BIM.max(3n, 7n)).toEqual(7n);
+    expect(BIM.max(6n, 6n)).toEqual(6n);
+    expect(BIM.max(-7n, -12n)).toEqual(-7n);
+  });
+});
+
+describe('min', () => {
+  it('should return the smaller of two numbers', () => {
+    expect(BIM.min(5n, 8n)).toEqual(5n);
+    expect(BIM.min(3n, 7n)).toEqual(3n);
+    expect(BIM.min(6n, 6n)).toEqual(6n);
+    expect(BIM.min(-7n, -12n)).toEqual(-12n);
+  });
+});
+
+describe('popcount', () => {
+  it('should return the number of set bits in an integer', () => {
+    expect(BIM.popcount(0n)).toBe(0);
+    expect(BIM.popcount(1n)).toBe(1);
+    expect(BIM.popcount(2n)).toBe(1);
+    expect(BIM.popcount(3n)).toBe(2);
+    expect(BIM.popcount(4n)).toBe(1);
+    expect(BIM.popcount(5n)).toBe(2);
+    expect(BIM.popcount(3462151285050974216n)).toBe(10);
+  });
+
+  it('should throw when presented with a negative integer', () => {
+    expect(() => BIM.popcount(-1n))
+        .toThrowError('Can\'t get popcount of negative number -1');
+  });
+});
diff --git a/ui/src/base/math_utils_unittest.ts b/ui/src/base/math_utils_unittest.ts
deleted file mode 100644
index 169b793..0000000
--- a/ui/src/base/math_utils_unittest.ts
+++ /dev/null
@@ -1,29 +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 {roundDownNearest, roundUpNearest} from './math_utils';
-
-describe('roundUpNearest()', () => {
-  it('rounds decimal values up to the right step size', () => {
-    expect(roundUpNearest(0.1, 0.5)).toBeCloseTo(0.5);
-    expect(roundUpNearest(17.2, 0.5)).toBeCloseTo(17.5);
-  });
-});
-
-describe('roundDownNearest()', () => {
-  it('rounds decimal values down to the right step size', () => {
-    expect(roundDownNearest(0.4, 0.5)).toBeCloseTo(0.0);
-    expect(roundDownNearest(17.4, 0.5)).toBeCloseTo(17.0);
-  });
-});
diff --git a/ui/src/common/actions.ts b/ui/src/common/actions.ts
index 1e602fc..286f82f 100644
--- a/ui/src/common/actions.ts
+++ b/ui/src/common/actions.ts
@@ -61,7 +61,7 @@
   UtidToTrackSortKey,
   VisibleState,
 } from './state';
-import {toNs} from './time';
+import {TPDuration, TPTime} from './time';
 
 export const DEBUG_SLICE_TRACK_KIND = 'DebugSliceTrack';
 
@@ -89,8 +89,8 @@
 }
 
 export interface PostedScrollToRange {
-  timeStart: number;
-  timeEnd: number;
+  timeStart: TPTime;
+  timeEnd: TPTime;
   viewPercentage?: number;
 }
 
@@ -552,7 +552,7 @@
 
   addAutomaticNote(
       state: StateDraft,
-      args: {timestamp: number, color: string, text: string}): void {
+      args: {timestamp: TPTime, color: string, text: string}): void {
     const id = generateNextId(state);
     state.notes[id] = {
       noteType: 'DEFAULT',
@@ -563,7 +563,7 @@
     };
   },
 
-  addNote(state: StateDraft, args: {timestamp: number, color: string}): void {
+  addNote(state: StateDraft, args: {timestamp: TPTime, color: string}): void {
     const id = generateNextId(state);
     state.notes[id] = {
       noteType: 'DEFAULT',
@@ -606,14 +606,10 @@
   },
 
   markArea(state: StateDraft, args: {area: Area, persistent: boolean}): void {
+    const {start, end, tracks} = args.area;
+    assertTrue(start <= end);
     const areaId = generateNextId(state);
-    assertTrue(args.area.endSec >= args.area.startSec);
-    state.areas[areaId] = {
-      id: areaId,
-      startSec: args.area.startSec,
-      endSec: args.area.endSec,
-      tracks: args.area.tracks,
-    };
+    state.areas[areaId] = {id: areaId, start, end, tracks};
     const noteId = args.persistent ? generateNextId(state) : '0';
     const color = args.persistent ? randomColor() : '#344596';
     state.notes[noteId] = {
@@ -667,7 +663,7 @@
 
   selectCounter(
       state: StateDraft,
-      args: {leftTs: number, rightTs: number, id: number, trackId: string}):
+      args: {leftTs: TPTime, rightTs: TPTime, id: number, trackId: string}):
       void {
         state.currentSelection = {
           kind: 'COUNTER',
@@ -680,7 +676,7 @@
 
   selectHeapProfile(
       state: StateDraft,
-      args: {id: number, upid: number, ts: number, type: ProfileType}): void {
+      args: {id: number, upid: number, ts: TPTime, type: ProfileType}): void {
     state.currentSelection = {
       kind: 'HEAP_PROFILE',
       id: args.id,
@@ -690,8 +686,8 @@
     };
     this.openFlamegraph(state, {
       type: args.type,
-      startNs: toNs(state.traceTime.startSec),
-      endNs: args.ts,
+      start: state.traceTime.start,
+      end: args.ts,
       upids: [args.upid],
       viewingOption: DEFAULT_VIEWING_OPTION,
     });
@@ -700,8 +696,8 @@
   selectPerfSamples(state: StateDraft, args: {
     id: number,
     upid: number,
-    leftTs: number,
-    rightTs: number,
+    leftTs: TPTime,
+    rightTs: TPTime,
     type: ProfileType
   }): void {
     state.currentSelection = {
@@ -714,8 +710,8 @@
     };
     this.openFlamegraph(state, {
       type: args.type,
-      startNs: args.leftTs,
-      endNs: args.rightTs,
+      start: args.leftTs,
+      end: args.rightTs,
       upids: [args.upid],
       viewingOption: PERF_SAMPLES_KEY,
     });
@@ -723,16 +719,16 @@
 
   openFlamegraph(state: StateDraft, args: {
     upids: number[],
-    startNs: number,
-    endNs: number,
+    start: TPTime,
+    end: TPTime,
     type: ProfileType,
     viewingOption: FlamegraphStateViewingOption
   }): void {
     state.currentFlamegraphState = {
       kind: 'FLAMEGRAPH_STATE',
       upids: args.upids,
-      startNs: args.startNs,
-      endNs: args.endNs,
+      start: args.start,
+      end: args.end,
       type: args.type,
       viewingOption: args.viewingOption,
       focusRegex: '',
@@ -784,16 +780,33 @@
   selectDebugSlice(state: StateDraft, args: {
     id: number,
     sqlTableName: string,
-    startS: number,
-    durationS: number,
+    start: TPTime,
+    duration: TPDuration,
     trackId: string,
   }): void {
     state.currentSelection = {
       kind: 'DEBUG_SLICE',
       id: args.id,
       sqlTableName: args.sqlTableName,
-      startS: args.startS,
-      durationS: args.durationS,
+      start: args.start,
+      duration: args.duration,
+      trackId: args.trackId,
+    };
+  },
+
+  selectTopLevelScrollSlice(state: StateDraft, args: {
+    id: number,
+    sqlTableName: string,
+    start: TPTime,
+    duration: TPTime,
+    trackId: string,
+  }): void {
+    state.currentSelection = {
+      kind: 'TOP_LEVEL_SCROLL',
+      id: args.id,
+      sqlTableName: args.sqlTableName,
+      start: args.start,
+      duration: args.duration,
       trackId: args.trackId,
     };
   },
@@ -893,25 +906,17 @@
   },
 
   selectArea(state: StateDraft, args: {area: Area}): void {
+    const {start, end, tracks} = args.area;
+    assertTrue(start <= end);
     const areaId = generateNextId(state);
-    assertTrue(args.area.endSec >= args.area.startSec);
-    state.areas[areaId] = {
-      id: areaId,
-      startSec: args.area.startSec,
-      endSec: args.area.endSec,
-      tracks: args.area.tracks,
-    };
+    state.areas[areaId] = {id: areaId, start, end, tracks};
     state.currentSelection = {kind: 'AREA', areaId};
   },
 
   editArea(state: StateDraft, args: {area: Area, areaId: string}): void {
-    assertTrue(args.area.endSec >= args.area.startSec);
-    state.areas[args.areaId] = {
-      id: args.areaId,
-      startSec: args.area.startSec,
-      endSec: args.area.endSec,
-      tracks: args.area.tracks,
-    };
+    const {start, end, tracks} = args.area;
+    assertTrue(start <= end);
+    state.areas[args.areaId] = {id: args.areaId, start, end, tracks};
   },
 
   reSelectArea(state: StateDraft, args: {areaId: string, noteId: string}):
@@ -1031,11 +1036,11 @@
     state.searchIndex = args.index;
   },
 
-  setHoverCursorTimestamp(state: StateDraft, args: {ts: number}) {
+  setHoverCursorTimestamp(state: StateDraft, args: {ts: TPTime}) {
     state.hoverCursorTimestamp = args.ts;
   },
 
-  setHoveredNoteTimestamp(state: StateDraft, args: {ts: number}) {
+  setHoveredNoteTimestamp(state: StateDraft, args: {ts: TPTime}) {
     state.hoveredNoteTimestamp = args.ts;
   },
 
diff --git a/ui/src/common/actions_unittest.ts b/ui/src/common/actions_unittest.ts
index 6702e72..b613777 100644
--- a/ui/src/common/actions_unittest.ts
+++ b/ui/src/common/actions_unittest.ts
@@ -446,9 +446,13 @@
   const state = createEmptyState();
 
   const afterSelectingPerf = produce(state, (draft) => {
-    StateActions.selectPerfSamples(
-        draft,
-        {id: 0, upid: 0, leftTs: 0, rightTs: 0, type: ProfileType.PERF_SAMPLE});
+    StateActions.selectPerfSamples(draft, {
+      id: 0,
+      upid: 0,
+      leftTs: 0n,
+      rightTs: 0n,
+      type: ProfileType.PERF_SAMPLE,
+    });
   });
 
   expect(assertExists(afterSelectingPerf.currentFlamegraphState).type)
@@ -460,7 +464,7 @@
 
   const afterSelectingPerf = produce(state, (draft) => {
     StateActions.selectHeapProfile(
-        draft, {id: 0, upid: 0, ts: 0, type: ProfileType.JAVA_HEAP_GRAPH});
+        draft, {id: 0, upid: 0, ts: 0n, type: ProfileType.JAVA_HEAP_GRAPH});
   });
 
   expect(assertExists(afterSelectingPerf.currentFlamegraphState).type)
diff --git a/ui/src/common/canvas_utils.ts b/ui/src/common/canvas_utils.ts
index aef36a0..d086f5b 100644
--- a/ui/src/common/canvas_utils.ts
+++ b/ui/src/common/canvas_utils.ts
@@ -86,5 +86,11 @@
   ctx.lineTo(x + width - 3, y + (triangleSize * 3.5));
   ctx.lineTo(x + width, y + 4 * triangleSize);
   ctx.lineTo(x, y + height);
+
+  const gradient = ctx.createLinearGradient(x, y, x + width, y + height);
+  gradient.addColorStop(0.66, ctx.fillStyle as string);
+  gradient.addColorStop(1, '#FFFFFF');
+  ctx.fillStyle = gradient;
+
   ctx.fill();
 }
diff --git a/ui/src/common/empty_state.ts b/ui/src/common/empty_state.ts
index bd1e6b6..6d806a2 100644
--- a/ui/src/common/empty_state.ts
+++ b/ui/src/common/empty_state.ts
@@ -110,7 +110,7 @@
       visibleState: {
         ...defaultTraceTime,
         lastUpdate: 0,
-        resolution: 0,
+        resolution: 0n,
       },
     },
 
@@ -142,8 +142,8 @@
     sidebarVisible: true,
     hoveredUtid: -1,
     hoveredPid: -1,
-    hoverCursorTimestamp: -1,
-    hoveredNoteTimestamp: -1,
+    hoverCursorTimestamp: -1n,
+    hoveredNoteTimestamp: -1n,
     highlightedSliceId: -1,
     focusedFlowIdLeft: -1,
     focusedFlowIdRight: -1,
diff --git a/ui/src/common/engine.ts b/ui/src/common/engine.ts
index 8b6cb46..861601f 100644
--- a/ui/src/common/engine.ts
+++ b/ui/src/common/engine.ts
@@ -24,18 +24,20 @@
   QueryArgs,
   ResetTraceProcessorArgs,
 } from './protos';
-import {NUM, NUM_NULL, STR} from './query_result';
+import {LONG, LONG_NULL, NUM, STR} from './query_result';
 import {
   createQueryResult,
   QueryError,
   QueryResult,
   WritableQueryResult,
 } from './query_result';
-import {TimeSpan} from './time';
+import {TPTime, TPTimeSpan} from './time';
 
 import TraceProcessorRpc = perfetto.protos.TraceProcessorRpc;
 import TraceProcessorRpcStream = perfetto.protos.TraceProcessorRpcStream;
 import TPM = perfetto.protos.TraceProcessorRpc.TraceProcessorMethod;
+import {Span} from '../common/time';
+import {BigintMath} from '../base/bigint_math';
 
 export interface LoadingTracker {
   beginLoading(): void;
@@ -410,38 +412,38 @@
     return result.firstRow({cnt: NUM}).cnt;
   }
 
-  async getTraceTimeBounds(): Promise<TimeSpan> {
+  async getTraceTimeBounds(): Promise<Span<TPTime>> {
     const result = await this.query(
         `select start_ts as startTs, end_ts as endTs from trace_bounds`);
     const bounds = result.firstRow({
-      startTs: NUM,
-      endTs: NUM,
+      startTs: LONG,
+      endTs: LONG,
     });
-    return new TimeSpan(bounds.startTs / 1e9, bounds.endTs / 1e9);
+    return new TPTimeSpan(bounds.startTs, bounds.endTs);
   }
 
-  async getTracingMetadataTimeBounds(): Promise<TimeSpan> {
+  async getTracingMetadataTimeBounds(): Promise<Span<TPTime>> {
     const queryRes = await this.query(`select
          name,
          int_value as intValue
          from metadata
          where name = 'tracing_started_ns' or name = 'tracing_disabled_ns'
          or name = 'all_data_source_started_ns'`);
-    let startBound = -Infinity;
-    let endBound = Infinity;
-    const it = queryRes.iter({'name': STR, 'intValue': NUM_NULL});
+    let startBound = 0n;
+    let endBound = BigintMath.INT64_MAX;
+    const it = queryRes.iter({'name': STR, 'intValue': LONG_NULL});
     for (; it.valid(); it.next()) {
       const columnName = it.name;
       const timestamp = it.intValue;
       if (timestamp === null) continue;
       if (columnName === 'tracing_disabled_ns') {
-        endBound = Math.min(endBound, timestamp / 1e9);
+        endBound = BigintMath.min(endBound, timestamp);
       } else {
-        startBound = Math.max(startBound, timestamp / 1e9);
+        startBound = BigintMath.max(startBound, timestamp);
       }
     }
 
-    return new TimeSpan(startBound, endBound);
+    return new TPTimeSpan(startBound, endBound);
   }
 
   getProxy(tag: string): EngineProxy {
diff --git a/ui/src/common/high_precision_time.ts b/ui/src/common/high_precision_time.ts
new file mode 100644
index 0000000..927b7df
--- /dev/null
+++ b/ui/src/common/high_precision_time.ts
@@ -0,0 +1,258 @@
+// 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 {assertTrue} from '../base/logging';
+import {Span, TPTime} from './time';
+
+export type RoundMode = 'round'|'floor'|'ceil';
+
+// Stores a time as a bigint and an offset which is capable of:
+// - Storing and reproducing "TPTime"s without losing precision.
+// - Storing time with sub-nanosecond precision.
+// This class is immutable - each operation returns a new object.
+export class HighPrecisionTime {
+  // Time in nanoseconds == base + offset
+  // offset is kept in the range 0 <= x < 1 to avoid losing precision
+  readonly base: bigint;
+  readonly offset: number;
+
+  static get ZERO(): HighPrecisionTime {
+    return new HighPrecisionTime(0n);
+  }
+
+  constructor(base: bigint = 0n, offset: number = 0) {
+    // Normalize offset to sit in the range 0.0 <= x < 1.0
+    const offsetFloor = Math.floor(offset);
+    this.base = base + BigInt(offsetFloor);
+    this.offset = offset - offsetFloor;
+  }
+
+  static fromTPTime(timestamp: TPTime): HighPrecisionTime {
+    return new HighPrecisionTime(timestamp, 0);
+  }
+
+  static fromNanos(nanos: number|bigint) {
+    if (typeof nanos === 'number') {
+      return new HighPrecisionTime(0n, nanos);
+    } else if (typeof nanos === 'bigint') {
+      return new HighPrecisionTime(nanos);
+    } else {
+      const value: never = nanos;
+      throw new Error(`Value ${value} is neither a number nor a bigint`);
+    }
+  }
+
+  static fromSeconds(seconds: number) {
+    const nanos = seconds * 1e9;
+    const offset = nanos - Math.floor(nanos);
+    return new HighPrecisionTime(BigInt(Math.floor(nanos)), offset);
+  }
+
+  static max(a: HighPrecisionTime, b: HighPrecisionTime): HighPrecisionTime {
+    return a.isGreaterThan(b) ? a : b;
+  }
+
+  static min(a: HighPrecisionTime, b: HighPrecisionTime): HighPrecisionTime {
+    return a.isLessThan(b) ? a : b;
+  }
+
+  toTPTime(roundMode: RoundMode = 'floor'): TPTime {
+    switch (roundMode) {
+      case 'round':
+        return this.base + BigInt(Math.round(this.offset));
+      case 'floor':
+        return this.base;
+      case 'ceil':
+        return this.base + BigInt(Math.ceil(this.offset));
+      default:
+        const exhaustiveCheck: never = roundMode;
+        throw new Error(`Unhandled roundMode case: ${exhaustiveCheck}`);
+    }
+  }
+
+  get nanos(): number {
+    // WARNING: Number(bigint) can be surprisingly slow.
+    // WARNING: Precision may be lost here.
+    return Number(this.base) + this.offset;
+  }
+
+  get seconds(): number {
+    // WARNING: Number(bigint) can be surprisingly slow.
+    // WARNING: Precision may be lost here.
+    return (Number(this.base) + this.offset) / 1e9;
+  }
+
+  add(other: HighPrecisionTime): HighPrecisionTime {
+    return new HighPrecisionTime(
+        this.base + other.base, this.offset + other.offset);
+  }
+
+  addNanos(nanos: number|bigint): HighPrecisionTime {
+    return this.add(HighPrecisionTime.fromNanos(nanos));
+  }
+
+  addSeconds(seconds: number): HighPrecisionTime {
+    return new HighPrecisionTime(this.base, this.offset + seconds * 1e9);
+  }
+
+  addTPTime(ts: TPTime): HighPrecisionTime {
+    return new HighPrecisionTime(this.base + ts, this.offset);
+  }
+
+  subtract(other: HighPrecisionTime): HighPrecisionTime {
+    return new HighPrecisionTime(
+        this.base - other.base, this.offset - other.offset);
+  }
+
+  subtractTPTime(ts: TPTime): HighPrecisionTime {
+    return this.addTPTime(-ts);
+  }
+
+  subtractNanos(nanos: number|bigint): HighPrecisionTime {
+    return this.add(HighPrecisionTime.fromNanos(-nanos));
+  }
+
+  divide(divisor: number): HighPrecisionTime {
+    return this.multiply(1 / divisor);
+  }
+
+  multiply(factor: number): HighPrecisionTime {
+    const factorFloor = Math.floor(factor);
+    const newBase = this.base * BigInt(factorFloor);
+    const additionalBit = Number(this.base) * (factor - factorFloor);
+    const newOffset = factor * this.offset + additionalBit;
+    return new HighPrecisionTime(newBase, newOffset);
+  }
+
+  // Return true if other time is within some epsilon, default 1 femtosecond
+  equals(other: HighPrecisionTime, epsilon: number = 1e-6): boolean {
+    return Math.abs(this.subtract(other).nanos) < epsilon;
+  }
+
+  isLessThan(other: HighPrecisionTime): boolean {
+    if (this.base < other.base) {
+      return true;
+    } else if (this.base === other.base) {
+      return this.offset < other.offset;
+    } else {
+      return false;
+    }
+  }
+
+  isLessThanOrEqual(other: HighPrecisionTime): boolean {
+    if (this.equals(other)) {
+      return true;
+    } else {
+      return this.isLessThan(other);
+    }
+  }
+
+  isGreaterThan(other: HighPrecisionTime): boolean {
+    return !this.isLessThanOrEqual(other);
+  }
+
+  isGreaterThanOrEqual(other: HighPrecisionTime): boolean {
+    return !this.isLessThan(other);
+  }
+
+  clamp(lower: HighPrecisionTime, upper: HighPrecisionTime): HighPrecisionTime {
+    if (this.isLessThan(lower)) {
+      return lower;
+    } else if (this.isGreaterThan(upper)) {
+      return upper;
+    } else {
+      return this;
+    }
+  }
+
+  toString(): string {
+    const offsetAsString = this.offset.toString();
+    if (offsetAsString === '0') {
+      return this.base.toString();
+    } else {
+      return `${this.base}${offsetAsString.substring(1)}`;
+    }
+  }
+}
+
+export class HighPrecisionTimeSpan implements Span<HighPrecisionTime> {
+  readonly start: HighPrecisionTime;
+  readonly end: HighPrecisionTime;
+
+  constructor(start: TPTime|HighPrecisionTime, end: TPTime|HighPrecisionTime) {
+    this.start = (start instanceof HighPrecisionTime) ?
+        start :
+        HighPrecisionTime.fromTPTime(start);
+    this.end = (end instanceof HighPrecisionTime) ?
+        end :
+        HighPrecisionTime.fromTPTime(end);
+    assertTrue(
+        this.start.isLessThanOrEqual(this.end),
+        `TimeSpan start [${this.start}] cannot be greater than end [${
+            this.end}]`);
+  }
+
+  static fromTpTime(start: TPTime, end: TPTime): HighPrecisionTimeSpan {
+    return new HighPrecisionTimeSpan(
+        HighPrecisionTime.fromTPTime(start),
+        HighPrecisionTime.fromTPTime(end),
+    );
+  }
+
+  static get ZERO(): HighPrecisionTimeSpan {
+    return new HighPrecisionTimeSpan(
+        HighPrecisionTime.ZERO,
+        HighPrecisionTime.ZERO,
+    );
+  }
+
+  get duration(): HighPrecisionTime {
+    return this.end.subtract(this.start);
+  }
+
+  get midpoint(): HighPrecisionTime {
+    return this.start.add(this.end).divide(2);
+  }
+
+  equals(other: Span<HighPrecisionTime>): boolean {
+    return this.start.equals(other.start) && this.end.equals(other.end);
+  }
+
+  contains(x: HighPrecisionTime|Span<HighPrecisionTime>): boolean {
+    if (x instanceof HighPrecisionTime) {
+      return this.start.isLessThanOrEqual(x) && x.isLessThan(this.end);
+    } else {
+      return this.start.isLessThanOrEqual(x.start) &&
+          x.end.isLessThanOrEqual(this.end);
+    }
+  }
+
+  intersects(x: Span<HighPrecisionTime>): boolean {
+    return !(
+        x.end.isLessThanOrEqual(this.start) ||
+        x.start.isGreaterThanOrEqual(this.end));
+  }
+
+  add(time: HighPrecisionTime): Span<HighPrecisionTime> {
+    return new HighPrecisionTimeSpan(this.start.add(time), this.end.add(time));
+  }
+
+  // Move the start and end away from each other a certain amount
+  pad(time: HighPrecisionTime): Span<HighPrecisionTime> {
+    return new HighPrecisionTimeSpan(
+        this.start.subtract(time),
+        this.end.add(time),
+    );
+  }
+}
diff --git a/ui/src/common/high_precision_time_unittest.ts b/ui/src/common/high_precision_time_unittest.ts
new file mode 100644
index 0000000..c3a2033
--- /dev/null
+++ b/ui/src/common/high_precision_time_unittest.ts
@@ -0,0 +1,308 @@
+// 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 {
+  HighPrecisionTime as HPTime,
+  HighPrecisionTimeSpan as HPTimeSpan,
+} from './high_precision_time';
+import {TPTime} from './time';
+
+// Quick 'n' dirty function to convert a string to a HPtime
+// Used to make tests more readable
+// E.g. '1.3' -> {base: 1, offset: 0.3}
+// E.g. '-0.3' -> {base: -1, offset: 0.7}
+function mkTime(time: string): HPTime {
+  const array = time.split('.');
+  if (array.length > 2) throw new Error(`Bad time format ${time}`);
+  const [base, fractions] = array;
+  const negative = time.startsWith('-');
+  const numBase = BigInt(base);
+
+  if (fractions) {
+    const numFractions = Number(`0.${fractions}`);
+    if (negative) {
+      return new HPTime(numBase - 1n, 1.0 - numFractions);
+    } else {
+      return new HPTime(numBase, numFractions);
+    }
+  } else {
+    return new HPTime(numBase);
+  }
+}
+
+function mkSpan(t1: string, t2: string): HPTimeSpan {
+  return new HPTimeSpan(mkTime(t1), mkTime(t2));
+}
+
+describe('Time', () => {
+  it('should create a new Time object with the given base and offset', () => {
+    const time = new HPTime(136n, 0.3);
+    expect(time.base).toBe(136n);
+    expect(time.offset).toBeCloseTo(0.3);
+  });
+
+  it('should normalize when offset is >= 1', () => {
+    let time = new HPTime(1n, 2.3);
+    expect(time.base).toBe(3n);
+    expect(time.offset).toBeCloseTo(0.3);
+
+    time = new HPTime(1n, 1);
+    expect(time.base).toBe(2n);
+    expect(time.offset).toBeCloseTo(0);
+  });
+
+  it('should normalize when offset is < 0', () => {
+    const time = new HPTime(1n, -0.4);
+    expect(time.base).toBe(0n);
+    expect(time.offset).toBeCloseTo(0.6);
+  });
+
+  it('should store timestamps without losing precision', () => {
+    let time = HPTime.fromTPTime(123n as TPTime);
+    expect(time.toTPTime()).toBe(123n as TPTime);
+
+    time = HPTime.fromTPTime(1152921504606846976n as TPTime);
+    expect(time.toTPTime()).toBe(1152921504606846976n as TPTime);
+  });
+
+  it('should store and manipulate timestamps without losing precision', () => {
+    let time = HPTime.fromTPTime(123n as TPTime);
+    time = time.addTPTime(456n);
+    expect(time.toTPTime()).toBe(579n);
+
+    time = HPTime.fromTPTime(2315700508990407843n as TPTime);
+    time = time.addTPTime(2315718101717517451n as TPTime);
+    expect(time.toTPTime()).toBe(4631418610707925294n);
+  });
+
+  it('should add time', () => {
+    const time1 = mkTime('1.3');
+    const time2 = mkTime('3.1');
+    const result = time1.add(time2);
+    expect(result.base).toEqual(4n);
+    expect(result.offset).toBeCloseTo(0.4);
+  });
+
+  it('should subtract time', () => {
+    const time1 = mkTime('3.1');
+    const time2 = mkTime('1.3');
+    const result = time1.subtract(time2);
+    expect(result.base).toEqual(1n);
+    expect(result.offset).toBeCloseTo(0.8);
+  });
+
+  it('should add nanoseconds', () => {
+    const time = mkTime('1.3');
+    const result = time.addNanos(0.8);
+    expect(result.base).toEqual(2n);
+    expect(result.offset).toBeCloseTo(0.1);
+  });
+
+  it('should add seconds', () => {
+    const time = mkTime('1.3');
+    const result = time.addSeconds(0.008);
+    expect(result.base).toEqual(8000001n);
+    expect(result.offset).toBeCloseTo(0.3);
+  });
+
+  it('should perform gt comparisions', () => {
+    const time = mkTime('1.2');
+    expect(time.isGreaterThanOrEqual(mkTime('0.5'))).toBeTruthy();
+    expect(time.isGreaterThanOrEqual(mkTime('1.1'))).toBeTruthy();
+    expect(time.isGreaterThanOrEqual(mkTime('1.2'))).toBeTruthy();
+    expect(time.isGreaterThanOrEqual(mkTime('1.5'))).toBeFalsy();
+    expect(time.isGreaterThanOrEqual(mkTime('5.5'))).toBeFalsy();
+  });
+
+  it('should perform gte comparisions', () => {
+    const time = mkTime('1.2');
+    expect(time.isGreaterThan(mkTime('0.5'))).toBeTruthy();
+    expect(time.isGreaterThan(mkTime('1.1'))).toBeTruthy();
+    expect(time.isGreaterThan(mkTime('1.2'))).toBeFalsy();
+    expect(time.isGreaterThan(mkTime('1.5'))).toBeFalsy();
+    expect(time.isGreaterThan(mkTime('5.5'))).toBeFalsy();
+  });
+
+  it('should perform lt comparisions', () => {
+    const time = mkTime('1.2');
+    expect(time.isLessThan(mkTime('0.5'))).toBeFalsy();
+    expect(time.isLessThan(mkTime('1.1'))).toBeFalsy();
+    expect(time.isLessThan(mkTime('1.2'))).toBeFalsy();
+    expect(time.isLessThan(mkTime('1.5'))).toBeTruthy();
+    expect(time.isLessThan(mkTime('5.5'))).toBeTruthy();
+  });
+
+  it('should perform lte comparisions', () => {
+    const time = mkTime('1.2');
+    expect(time.isLessThanOrEqual(mkTime('0.5'))).toBeFalsy();
+    expect(time.isLessThanOrEqual(mkTime('1.1'))).toBeFalsy();
+    expect(time.isLessThanOrEqual(mkTime('1.2'))).toBeTruthy();
+    expect(time.isLessThanOrEqual(mkTime('1.5'))).toBeTruthy();
+    expect(time.isLessThanOrEqual(mkTime('5.5'))).toBeTruthy();
+  });
+
+  it('should detect equality', () => {
+    const time = new HPTime(1n, 0.2);
+    expect(time.equals(new HPTime(1n, 0.2))).toBeTruthy();
+    expect(time.equals(new HPTime(0n, 1.2))).toBeTruthy();
+    expect(time.equals(new HPTime(-100n, 101.2))).toBeTruthy();
+    expect(time.equals(new HPTime(1n, 0.3))).toBeFalsy();
+    expect(time.equals(new HPTime(2n, 0.2))).toBeFalsy();
+  });
+
+  it('should clamp a time to a range', () => {
+    const time1 = mkTime('1.2');
+    const time2 = mkTime('5.4');
+    const time3 = mkTime('2.8');
+    const lower = mkTime('2.3');
+    const upper = mkTime('4.5');
+    expect(time1.clamp(lower, upper)).toEqual(lower);
+    expect(time2.clamp(lower, upper)).toEqual(upper);
+    expect(time3.clamp(lower, upper)).toEqual(time3);
+  });
+
+  it('should convert to seconds', () => {
+    expect(new HPTime(1n, .2).seconds).toBeCloseTo(0.0000000012);
+    expect(new HPTime(1000000000n, .0).seconds).toBeCloseTo(1);
+  });
+
+  it('should convert to nanos', () => {
+    expect(new HPTime(1n, .2).nanos).toBeCloseTo(1.2);
+    expect(new HPTime(1000000000n, .0).nanos).toBeCloseTo(1e9);
+  });
+
+  it('should convert to timestamps', () => {
+    expect(new HPTime(1n, .2).toTPTime('round')).toBe(1n);
+    expect(new HPTime(1n, .5).toTPTime('round')).toBe(2n);
+    expect(new HPTime(1n, .2).toTPTime('floor')).toBe(1n);
+    expect(new HPTime(1n, .5).toTPTime('floor')).toBe(1n);
+    expect(new HPTime(1n, .2).toTPTime('ceil')).toBe(2n);
+    expect(new HPTime(1n, .5).toTPTime('ceil')).toBe(2n);
+  });
+
+  it('should divide', () => {
+    let result = mkTime('1').divide(2);
+    expect(result.base).toBe(0n);
+    expect(result.offset).toBeCloseTo(0.5);
+
+    result = mkTime('1.6').divide(2);
+    expect(result.base).toBe(0n);
+    expect(result.offset).toBeCloseTo(0.8);
+
+    result = mkTime('-0.5').divide(2);
+    expect(result.base).toBe(-1n);
+    expect(result.offset).toBeCloseTo(0.75);
+
+    result = mkTime('123.1').divide(123);
+    expect(result.base).toBe(1n);
+    expect(result.offset).toBeCloseTo(0.000813, 6);
+  });
+
+  it('should multiply', () => {
+    let result = mkTime('1').multiply(2);
+    expect(result.base).toBe(2n);
+    expect(result.offset).toBeCloseTo(0);
+
+    result = mkTime('1').multiply(2.5);
+    expect(result.base).toBe(2n);
+    expect(result.offset).toBeCloseTo(0.5);
+
+    result = mkTime('-0.5').multiply(2);
+    expect(result.base).toBe(-1n);
+    expect(result.offset).toBeCloseTo(0.0);
+
+    result = mkTime('123.1').multiply(25.5);
+    expect(result.base).toBe(3139n);
+    expect(result.offset).toBeCloseTo(0.05);
+  });
+
+  it('should convert to string', () => {
+    expect(mkTime('1.3').toString()).toBe('1.3');
+    expect(mkTime('12983423847.332533').toString()).toBe('12983423847.332533');
+    expect(new HPTime(234n).toString()).toBe('234');
+  });
+});
+
+describe('HighPrecisionTimeSpan', () => {
+  it('can be constructed from HP time', () => {
+    const span = new HPTimeSpan(mkTime('10'), mkTime('20'));
+    expect(span.start).toEqual(mkTime('10'));
+    expect(span.end).toEqual(mkTime('20'));
+  });
+
+  it('can be constructed from integer time', () => {
+    const span = new HPTimeSpan(10n, 20n);
+    expect(span.start).toEqual(mkTime('10'));
+    expect(span.end).toEqual(mkTime('20'));
+  });
+
+  it('throws when start is later than end', () => {
+    expect(() => new HPTimeSpan(mkTime('0.1'), mkTime('0'))).toThrow();
+    expect(() => new HPTimeSpan(mkTime('1124.0001'), mkTime('1124'))).toThrow();
+  });
+
+  it('can calc duration', () => {
+    let dur = mkSpan('10', '20').duration;
+    expect(dur.base).toBe(10n);
+    expect(dur.offset).toBeCloseTo(0);
+
+    dur = mkSpan('10.123', '20.456').duration;
+    expect(dur.base).toBe(10n);
+    expect(dur.offset).toBeCloseTo(0.333);
+  });
+
+  it('can calc midpoint', () => {
+    let mid = mkSpan('10', '20').midpoint;
+    expect(mid.base).toBe(15n);
+    expect(mid.offset).toBeCloseTo(0);
+
+    mid = mkSpan('10.25', '16.75').midpoint;
+    expect(mid.base).toBe(13n);
+    expect(mid.offset).toBeCloseTo(0.5);
+  });
+
+  it('can be compared', () => {
+    expect(mkSpan('0.1', '34.2').equals(mkSpan('0.1', '34.2'))).toBeTruthy();
+    expect(mkSpan('0.1', '34.5').equals(mkSpan('0.1', '34.2'))).toBeFalsy();
+    expect(mkSpan('0.9', '34.2').equals(mkSpan('0.1', '34.2'))).toBeFalsy();
+  });
+
+  it('checks if span contains another span', () => {
+    const x = mkSpan('10', '20');
+
+    expect(x.contains(mkTime('9'))).toBeFalsy();
+    expect(x.contains(mkTime('10'))).toBeTruthy();
+    expect(x.contains(mkTime('15'))).toBeTruthy();
+    expect(x.contains(mkTime('20'))).toBeFalsy();
+    expect(x.contains(mkTime('21'))).toBeFalsy();
+
+    expect(x.contains(mkSpan('12', '18'))).toBeTruthy();
+    expect(x.contains(mkSpan('5', '25'))).toBeFalsy();
+    expect(x.contains(mkSpan('5', '15'))).toBeFalsy();
+    expect(x.contains(mkSpan('15', '25'))).toBeFalsy();
+    expect(x.contains(mkSpan('0', '10'))).toBeFalsy();
+    expect(x.contains(mkSpan('20', '30'))).toBeFalsy();
+  });
+
+  it('checks if span intersects another span', () => {
+    const x = mkSpan('10', '20');
+
+    expect(x.intersects(mkSpan('0', '10'))).toBeFalsy();
+    expect(x.intersects(mkSpan('5', '15'))).toBeTruthy();
+    expect(x.intersects(mkSpan('12', '18'))).toBeTruthy();
+    expect(x.intersects(mkSpan('15', '25'))).toBeTruthy();
+    expect(x.intersects(mkSpan('20', '30'))).toBeFalsy();
+    expect(x.intersects(mkSpan('5', '25'))).toBeTruthy();
+  });
+});
diff --git a/ui/src/common/internal_layout_utils.ts b/ui/src/common/internal_layout_utils.ts
new file mode 100644
index 0000000..478cd02
--- /dev/null
+++ b/ui/src/common/internal_layout_utils.ts
@@ -0,0 +1,50 @@
+// 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.
+
+// Object to facilitate generation of SELECT statement using
+// generateSqlWithInternalLayout.
+//
+// Fields:
+// @columns: a string array list of the columns to be selected from the table.
+// @layoutParams: a config of the timestamp (ts) and duration (dur) fields
+// required by the internal_layout function.
+// @sourceTable: the table in the FROM clause, source of the data.
+// @whereClause: the WHERE clause to filter data from the source table.
+// @orderByClause: the ORDER BY clause for the query data.
+interface GenerateSqlArgs {
+  columns: string[];
+  layoutParams: {ts: string, dur: string};
+  sourceTable: string;
+  whereClause?: string;
+  orderByClause?: string;
+}
+
+// Function to generate a SELECT statement utilizing the internal_layout
+// SQL function as a depth field.
+export function generateSqlWithInternalLayout(sqlArgs: GenerateSqlArgs):
+    string {
+  let sql = `SELECT ` + sqlArgs.columns.toString() + ', internal_layout(' +
+      sqlArgs.layoutParams.ts + ',' + sqlArgs.layoutParams.dur +
+      ') OVER (ORDER BY ' + sqlArgs.layoutParams.ts +
+      ' ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS depth' +
+      ' FROM ' + sqlArgs.sourceTable;
+  if (sqlArgs.whereClause !== undefined) {
+    sql += ' WHERE ' + sqlArgs.whereClause;
+  }
+  if (sqlArgs.orderByClause !== undefined) {
+    sql += ' ORDER BY ' + sqlArgs.orderByClause;
+  }
+  sql += ';';
+  return sql;
+}
diff --git a/ui/src/common/logs.ts b/ui/src/common/logs.ts
index 0fc2fae..04cf065 100644
--- a/ui/src/common/logs.ts
+++ b/ui/src/common/logs.ts
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {TPTime} from './time';
+
 export const LogExistsKey = 'log-exists';
 export const LogBoundsKey = 'log-bounds';
 export const LogEntriesKey = 'log-entries';
@@ -19,16 +21,16 @@
 export interface LogExists { exists: boolean; }
 
 export interface LogBounds {
-  startTs: number;
-  endTs: number;
-  firstRowTs: number;
-  lastRowTs: number;
-  total: number;
+  firstLogTs: TPTime;
+  lastLogTs: TPTime;
+  firstVisibleLogTs: TPTime;
+  lastVisibleLogTs: TPTime;
+  totalVisibleLogs: number;
 }
 
 export interface LogEntries {
   offset: number;
-  timestamps: number[];
+  timestamps: TPTime[];
   priorities: number[];
   tags: string[];
   messages: string[];
diff --git a/ui/src/common/plugin_api.ts b/ui/src/common/plugin_api.ts
index fa8a9fc..0dc66d6 100644
--- a/ui/src/common/plugin_api.ts
+++ b/ui/src/common/plugin_api.ts
@@ -15,9 +15,12 @@
 import {EngineProxy} from '../common/engine';
 import {TrackControllerFactory} from '../controller/track_controller';
 import {TrackCreator} from '../frontend/track';
+import {Selection} from './state';
 
 export {EngineProxy} from '../common/engine';
 export {
+  LONG,
+  LONG_NULL,
   NUM,
   NUM_NULL,
   STR,
@@ -66,6 +69,16 @@
   // could be registered in dev.perfetto.CounterTrack - a whole
   // different plugin.
   registerTrack(track: TrackCreator): void;
+
+  // Register custom functionality to specify how the plugin should handle
+  // selection changes for tracks in this plugin.
+  //
+  // Params:
+  // @onDetailsPanelSelectionChange a function that takes a Selection as its
+  // parameter and performs whatever must happen on the details panel when the
+  // selection is invoked.
+  registerOnDetailsPanelSelectionChange(
+      onDetailsPanelSelectionChange: (newSelection?: Selection) => void): void;
 }
 
 export interface PluginInfo {
diff --git a/ui/src/common/plugins.ts b/ui/src/common/plugins.ts
index aa414bc..5898c5f 100644
--- a/ui/src/common/plugins.ts
+++ b/ui/src/common/plugins.ts
@@ -27,12 +27,14 @@
   TrackProvider,
 } from './plugin_api';
 import {Registry} from './registry';
+import {Selection} from './state';
 
 // Every plugin gets its own PluginContext. This is how we keep track
 // what each plugin is doing and how we can blame issues on particular
 // plugins.
 export class PluginContextImpl implements PluginContext {
   readonly pluginId: string;
+  onDetailsPanelSelectionChange?: (newSelection?: Selection) => void;
   private trackProviders: TrackProvider[];
 
   constructor(pluginId: string) {
@@ -53,6 +55,11 @@
   registerTrackProvider(provider: TrackProvider) {
     this.trackProviders.push(provider);
   }
+
+  registerOnDetailsPanelSelectionChange(
+      onDetailsPanelSelectionChange: (newSelection?: Selection) => void) {
+    this.onDetailsPanelSelectionChange = onDetailsPanelSelectionChange;
+  }
   // ==================================================================
 
   // ==================================================================
@@ -123,6 +130,14 @@
     }
     return promises;
   }
+
+  onDetailsPanelSelectionChange(pluginId: string, newSelection?: Selection) {
+    const pluginContext = this.getPluginContext(pluginId);
+    if (pluginContext === undefined) return;
+    if (pluginContext.onDetailsPanelSelectionChange) {
+      pluginContext.onDetailsPanelSelectionChange(newSelection);
+    }
+  }
 }
 
 // TODO(hjd): Sort out the story for global singletons like these:
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index 995f643..91ca358 100644
--- a/ui/src/common/state.ts
+++ b/ui/src/common/state.ts
@@ -18,7 +18,10 @@
   PivotTree,
   TableColumn,
 } from '../frontend/pivot_table_types';
+import {TopLevelScrollSelection} from '../tracks/scroll_jank/scroll_track';
+
 import {Direction} from './event_set';
+import {TPDuration, TPTime} from './time';
 
 /**
  * A plain js object, holding objects of type |Class| keyed by string id.
@@ -39,9 +42,9 @@
 }
 
 export interface VisibleState extends Timestamped {
-  startSec: number;
-  endSec: number;
-  resolution: number;
+  start: TPTime;
+  end: TPTime;
+  resolution: TPDuration;
 }
 
 export interface AreaSelection {
@@ -59,8 +62,8 @@
 export type AreaById = Area&{id: string};
 
 export interface Area {
-  startSec: number;
-  endSec: number;
+  start: TPTime;
+  end: TPTime;
   tracks: string[];
 }
 
@@ -100,7 +103,8 @@
 // 28. Add a boolean indicating if non matching log entries are hidden.
 // 29. Add ftrace state. <-- Borked, state contains a non-serializable object.
 // 30. Convert ftraceFilter.excludedNames from Set<string> to string[].
-export const STATE_VERSION = 30;
+// 31. Convert all timestamps to bigints.
+export const STATE_VERSION = 31;
 
 export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
@@ -268,8 +272,8 @@
 }
 
 export interface TraceTime {
-  startSec: number;
-  endSec: number;
+  start: TPTime;
+  end: TPTime;
 }
 
 export interface FrontendLocalState {
@@ -284,7 +288,7 @@
 export interface Note {
   noteType: 'DEFAULT';
   id: string;
-  timestamp: number;
+  timestamp: TPTime;
   color: string;
   text: string;
 }
@@ -311,14 +315,14 @@
   kind: 'DEBUG_SLICE';
   id: number;
   sqlTableName: string;
-  startS: number;
-  durationS: number;
+  start: TPTime;
+  duration: TPDuration;
 }
 
 export interface CounterSelection {
   kind: 'COUNTER';
-  leftTs: number;
-  rightTs: number;
+  leftTs: TPTime;
+  rightTs: TPTime;
   id: number;
 }
 
@@ -326,7 +330,7 @@
   kind: 'HEAP_PROFILE';
   id: number;
   upid: number;
-  ts: number;
+  ts: TPTime;
   type: ProfileType;
 }
 
@@ -334,16 +338,16 @@
   kind: 'PERF_SAMPLES';
   id: number;
   upid: number;
-  leftTs: number;
-  rightTs: number;
+  leftTs: TPTime;
+  rightTs: TPTime;
   type: ProfileType;
 }
 
 export interface FlamegraphState {
   kind: 'FLAMEGRAPH_STATE';
   upids: number[];
-  startNs: number;
-  endNs: number;
+  start: TPTime;
+  end: TPTime;
   type: ProfileType;
   viewingOption: FlamegraphStateViewingOption;
   focusRegex: string;
@@ -377,8 +381,8 @@
 export type Selection =
     (NoteSelection|SliceSelection|CounterSelection|HeapProfileSelection|
      CpuProfileSampleSelection|ChromeSliceSelection|ThreadStateSelection|
-     AreaSelection|PerfSamplesSelection|LogSelection|DebugSliceSelection)&
-    {trackId?: string};
+     AreaSelection|PerfSamplesSelection|LogSelection|DebugSliceSelection|
+     TopLevelScrollSelection)&{trackId?: string};
 export type SelectionKind = Selection['kind'];  // 'THREAD_STATE' | 'SLICE' ...
 
 export interface Pagination {
@@ -570,8 +574,8 @@
   // Hovered and focused events
   hoveredUtid: number;
   hoveredPid: number;
-  hoverCursorTimestamp: number;
-  hoveredNoteTimestamp: number;
+  hoverCursorTimestamp: TPTime;
+  hoveredNoteTimestamp: TPTime;
   highlightedSliceId: number;
   focusedFlowIdLeft: number;
   focusedFlowIdRight: number;
@@ -608,8 +612,8 @@
 }
 
 export const defaultTraceTime = {
-  startSec: 0,
-  endSec: 10,
+  start: 0n,
+  end: BigInt(10e9),
 };
 
 export declare type RecordMode =
@@ -661,9 +665,9 @@
 
 export function getDefaultRecordingTargets(): RecordingTarget[] {
   return [
-    {os: 'Q', name: 'Android Q+'},
-    {os: 'P', name: 'Android P'},
-    {os: 'O', name: 'Android O-'},
+    {os: 'Q', name: 'Android Q+ / 10+'},
+    {os: 'P', name: 'Android P / 9'},
+    {os: 'O', name: 'Android O- / 8-'},
     {os: 'C', name: 'Chrome'},
     {os: 'CrOS', name: 'Chrome OS (system trace)'},
     {os: 'L', name: 'Linux desktop'},
diff --git a/ui/src/common/state_unittest.ts b/ui/src/common/state_unittest.ts
index 58610dc..9b5a064 100644
--- a/ui/src/common/state_unittest.ts
+++ b/ui/src/common/state_unittest.ts
@@ -14,6 +14,7 @@
 
 import {createEmptyState} from './empty_state';
 import {getContainingTrackId, PrimaryTrackSortKey, State} from './state';
+import {deserializeStateObject, serializeStateObject} from './upload_utils';
 
 test('createEmptyState', () => {
   const state: State = createEmptyState();
@@ -46,20 +47,10 @@
   expect(getContainingTrackId(state, 'b')).toEqual('containsB');
 });
 
-function serializeState(state: State): string {
-  return JSON.stringify(state, (key, value) => {
-    return key === 'nonSerializableState' ? undefined : value;
-  });
-}
-
-function deserializeState(json: string): State {
-  return JSON.parse(json);
-}
-
 test('state is serializable', () => {
   const state: State = createEmptyState();
-  const json = serializeState(state);
-  const restored: State = deserializeState(json);
+  const json = serializeStateObject(state);
+  const restored: State = deserializeStateObject(json);
 
   // Remove nonSerializableState from original
   const serializableState: any = state as any;
diff --git a/ui/src/common/time.ts b/ui/src/common/time.ts
index ea5e9d8..5608d3f 100644
--- a/ui/src/common/time.ts
+++ b/ui/src/common/time.ts
@@ -13,8 +13,7 @@
 // limitations under the License.
 
 import {assertTrue} from '../base/logging';
-
-const EPSILON = 0.0000000001;
+import {ColumnType} from './query_result';
 
 // TODO(hjd): Combine with timeToCode.
 export function timeToString(sec: number) {
@@ -29,6 +28,10 @@
   return `${sign < 0 ? '-' : ''}${Math.round(n * 10) / 10} ${units[u]}`;
 }
 
+export function tpTimeToString(time: TPTime) {
+  return timeToString(tpTimeToSeconds(time));
+}
+
 export function fromNs(ns: number) {
   return ns / 1e9;
 }
@@ -52,6 +55,10 @@
   return parts.join('.');
 }
 
+export function formatTPTime(time: TPTime) {
+  return formatTimestamp(tpTimeToSeconds(time));
+}
+
 // TODO(hjd): Rename to formatTimestampWithUnits
 // 1000000023ns -> "1s 23ns"
 export function timeToCode(sec: number): string {
@@ -77,44 +84,128 @@
   return result.slice(0, -1);
 }
 
+export function tpTimeToCode(time: TPTime) {
+  return timeToCode(tpTimeToSeconds(time));
+}
+
 export function currentDateHourAndMinute(): string {
   const date = new Date();
   return `${date.toISOString().substr(0, 10)}-${date.getHours()}-${
       date.getMinutes()}`;
 }
 
-export class TimeSpan {
-  readonly start: number;
-  readonly end: number;
+// Aliased "Trace Processor" time and duration types.
+// Note(stevegolton): While it might be nice to type brand these in the future,
+// for now we're going to keep things simple. We do a lot of maths with these
+// timestamps and type branding requires a lot of jumping through hoops to
+// coerse the type back to the correct format.
+export type TPTime = bigint;
+export type TPDuration = bigint;
 
-  constructor(start: number, end: number) {
-    assertTrue(start <= end);
+export function tpTimeFromNanos(nanos: number): TPTime {
+  return BigInt(Math.floor(nanos));
+}
+
+export function tpTimeFromSeconds(seconds: number): TPTime {
+  return BigInt(Math.floor(seconds * 1e9));
+}
+
+export function tpTimeToNanos(time: TPTime): number {
+  return Number(time);
+}
+
+export function tpTimeToMillis(time: TPTime): number {
+  return Number(time) / 1e6;
+}
+
+export function tpTimeToSeconds(time: TPTime): number {
+  return Number(time) / 1e9;
+}
+
+// Create a TPTime from an arbitrary SQL value.
+// Throws if the value cannot be reasonably converted to a bigint.
+// Assumes value is in nanoseconds.
+export function tpTimeFromSql(value: ColumnType): TPTime {
+  if (typeof value === 'bigint') {
+    return value;
+  } else if (typeof value === 'number') {
+    return tpTimeFromNanos(value);
+  } else if (value === null) {
+    return 0n;
+  } else {
+    throw Error(`Refusing to create Timestamp from unrelated type ${value}`);
+  }
+}
+
+export function tpDurationToSeconds(dur: TPDuration): number {
+  return tpTimeToSeconds(dur);
+}
+
+export function tpDurationToNanos(dur: TPDuration): number {
+  return tpTimeToSeconds(dur);
+}
+
+export function tpDurationFromNanos(nanos: number): TPDuration {
+  return tpTimeFromNanos(nanos);
+}
+
+export function tpDurationFromSql(nanos: ColumnType): TPDuration {
+  return tpTimeFromSql(nanos);
+}
+
+export interface Span<Unit, Duration = Unit> {
+  get start(): Unit;
+  get end(): Unit;
+  get duration(): Duration;
+  get midpoint(): Unit;
+  contains(span: Unit|Span<Unit, Duration>): boolean;
+  intersects(x: Span<Unit>): boolean;
+  equals(span: Span<Unit, Duration>): boolean;
+  add(offset: Duration): Span<Unit, Duration>;
+  pad(padding: Duration): Span<Unit, Duration>;
+}
+
+export class TPTimeSpan implements Span<TPTime, TPDuration> {
+  readonly start: TPTime;
+  readonly end: TPTime;
+
+  constructor(start: TPTime, end: TPTime) {
+    assertTrue(
+        start <= end,
+        `Span start [${start}] cannot be greater than end [${end}]`);
     this.start = start;
     this.end = end;
   }
 
-  clone() {
-    return new TimeSpan(this.start, this.end);
-  }
-
-  equals(other: TimeSpan): boolean {
-    return Math.abs(this.start - other.start) < EPSILON &&
-        Math.abs(this.end - other.end) < EPSILON;
-  }
-
-  get duration() {
+  get duration(): TPDuration {
     return this.end - this.start;
   }
 
-  isInBounds(sec: number) {
-    return this.start <= sec && sec <= this.end;
+  get midpoint(): TPTime {
+    return (this.start + this.end) / 2n;
   }
 
-  add(sec: number): TimeSpan {
-    return new TimeSpan(this.start + sec, this.end + sec);
+  contains(x: TPTime|Span<TPTime, TPDuration>): boolean {
+    if (typeof x === 'bigint') {
+      return this.start <= x && x < this.end;
+    } else {
+      return this.start <= x.start && x.end <= this.end;
+    }
   }
 
-  contains(other: TimeSpan) {
-    return this.start <= other.start && other.end <= this.end;
+  intersects(x: Span<TPTime, TPDuration>): boolean {
+    return !(x.end <= this.start || x.start >= this.end);
+  }
+
+  equals(span: Span<TPTime, TPDuration>): boolean {
+    return this.start === span.start && this.end === span.end;
+  }
+
+  add(x: TPTime): Span<TPTime, TPDuration> {
+    return new TPTimeSpan(this.start + x, this.end + x);
+  }
+
+  pad(padding: TPDuration): Span<TPTime, TPDuration> {
+    return new TPTimeSpan(this.start - padding, this.end + padding);
   }
 }
diff --git a/ui/src/common/time_unittest.ts b/ui/src/common/time_unittest.ts
index 7bfead1..b9e6bd9 100644
--- a/ui/src/common/time_unittest.ts
+++ b/ui/src/common/time_unittest.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {TimeSpan, timeToCode} from './time';
+import {timeToCode, TPTime, TPTimeSpan} from './time';
 
 test('seconds to code', () => {
   expect(timeToCode(3)).toEqual('3s');
@@ -29,9 +29,67 @@
   expect(timeToCode(0)).toEqual('0s');
 });
 
-test('Time span equality', () => {
-  expect((new TimeSpan(0, 1)).equals(new TimeSpan(0, 1))).toBe(true);
-  expect((new TimeSpan(0, 1)).equals(new TimeSpan(0, 2))).toBe(false);
-  expect((new TimeSpan(0, 1)).equals(new TimeSpan(0, 1 + Number.EPSILON)))
-      .toBe(true);
+function mkSpan(start: TPTime, end: TPTime) {
+  return new TPTimeSpan(start, end);
+}
+
+describe('TPTimeSpan', () => {
+  it('throws when start is later than end', () => {
+    expect(() => mkSpan(1n, 0n)).toThrow();
+  });
+
+  it('can calc duration', () => {
+    expect(mkSpan(10n, 20n).duration).toBe(10n);
+  });
+
+  it('can calc midpoint', () => {
+    expect(mkSpan(10n, 20n).midpoint).toBe(15n);
+    expect(mkSpan(10n, 19n).midpoint).toBe(14n);
+    expect(mkSpan(10n, 10n).midpoint).toBe(10n);
+  });
+
+  it('can be compared', () => {
+    const x = mkSpan(10n, 20n);
+    expect(x.equals(mkSpan(10n, 20n))).toBeTruthy();
+    expect(x.equals(mkSpan(11n, 20n))).toBeFalsy();
+    expect(x.equals(mkSpan(10n, 19n))).toBeFalsy();
+  });
+
+  it('checks containment', () => {
+    const x = mkSpan(10n, 20n);
+
+    expect(x.contains(9n)).toBeFalsy();
+    expect(x.contains(10n)).toBeTruthy();
+    expect(x.contains(15n)).toBeTruthy();
+    expect(x.contains(20n)).toBeFalsy();
+    expect(x.contains(21n)).toBeFalsy();
+
+    expect(x.contains(mkSpan(12n, 18n))).toBeTruthy();
+    expect(x.contains(mkSpan(5n, 25n))).toBeFalsy();
+    expect(x.contains(mkSpan(5n, 15n))).toBeFalsy();
+    expect(x.contains(mkSpan(15n, 25n))).toBeFalsy();
+    expect(x.contains(mkSpan(0n, 10n))).toBeFalsy();
+    expect(x.contains(mkSpan(20n, 30n))).toBeFalsy();
+  });
+
+  it('checks intersection', () => {
+    const x = mkSpan(10n, 20n);
+
+    expect(x.intersects(mkSpan(0n, 10n))).toBeFalsy();
+    expect(x.intersects(mkSpan(5n, 15n))).toBeTruthy();
+    expect(x.intersects(mkSpan(12n, 18n))).toBeTruthy();
+    expect(x.intersects(mkSpan(15n, 25n))).toBeTruthy();
+    expect(x.intersects(mkSpan(20n, 30n))).toBeFalsy();
+    expect(x.intersects(mkSpan(5n, 25n))).toBeTruthy();
+  });
+
+  it('can add', () => {
+    const x = mkSpan(10n, 20n);
+    expect(x.add(5n)).toEqual(mkSpan(15n, 25n));
+  });
+
+  it('can pad', () => {
+    const x = mkSpan(10n, 20n);
+    expect(x.pad(5n)).toEqual(mkSpan(5n, 25n));
+  });
 });
diff --git a/ui/src/common/track_data.ts b/ui/src/common/track_data.ts
index 9af7fff..a49a56d 100644
--- a/ui/src/common/track_data.ts
+++ b/ui/src/common/track_data.ts
@@ -12,12 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {TPDuration, TPTime} from './time';
+
 // TODO(hjd): Refactor into method on TrackController
 export const LIMIT = 10000;
 
 export interface TrackData {
-  start: number;
-  end: number;
-  resolution: number;
+  start: TPTime;
+  end: TPTime;
+  resolution: TPDuration;
   length: number;
 }
diff --git a/ui/src/common/upload_utils.ts b/ui/src/common/upload_utils.ts
index 875b072..0c97921 100644
--- a/ui/src/common/upload_utils.ts
+++ b/ui/src/common/upload_utils.ts
@@ -34,11 +34,53 @@
   return `https://storage.googleapis.com/${BUCKET_NAME}/${name}`;
 }
 
-export async function saveState(stateOrConfig: State|
-                                RecordConfig): Promise<string> {
-  const text = JSON.stringify(stateOrConfig, (key, value) => {
+// Bigint's are not serializable using JSON.stringify, so we use a special
+// object when serialising
+export type SerializedBigint = {
+  __kind: 'bigint',
+  value: string
+};
+
+// Check if a value looks like a serialized bigint
+export function isSerializedBigint(value: unknown): value is SerializedBigint {
+  if (value === null) {
+    return false;
+  }
+  if (typeof value !== 'object') {
+    return false;
+  }
+  if ('__kind' in value && 'value' in value) {
+    return value.__kind === 'bigint' && typeof value.value === 'string';
+  }
+  return false;
+}
+
+export function serializeStateObject(object: unknown): string {
+  const json = JSON.stringify(object, (key, value) => {
+    if (typeof value === 'bigint') {
+      return {
+        __kind: 'bigint',
+        value: value.toString(),
+      };
+    }
     return key === 'nonSerializableState' ? undefined : value;
   });
+  return json;
+}
+
+export function deserializeStateObject(json: string): any {
+  const object = JSON.parse(json, (_key, value) => {
+    if (isSerializedBigint(value)) {
+      return BigInt(value.value);
+    }
+    return value;
+  });
+  return object;
+}
+
+export async function saveState(stateOrConfig: State|
+                                RecordConfig): Promise<string> {
+  const text = serializeStateObject(stateOrConfig);
   const hash = await toSha256(text);
   const url = 'https://www.googleapis.com/upload/storage/v1/b/' +
       `${BUCKET_NAME}/o?uploadType=media` +
diff --git a/ui/src/common/upload_utils_unittest.ts b/ui/src/common/upload_utils_unittest.ts
new file mode 100644
index 0000000..4c0b8ce
--- /dev/null
+++ b/ui/src/common/upload_utils_unittest.ts
@@ -0,0 +1,105 @@
+// 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 {
+  deserializeStateObject,
+  isSerializedBigint,
+  serializeStateObject,
+} from './upload_utils';
+
+describe('isSerializedBigint', () => {
+  it('should return true for a valid serialized bigint', () => {
+    const value = {
+      __kind: 'bigint',
+      value: '1234567890',
+    };
+    expect(isSerializedBigint(value)).toBeTruthy();
+  });
+
+  it('should return false for a null value', () => {
+    expect(isSerializedBigint(null)).toBeFalsy();
+  });
+
+  it('should return false for a non-object value', () => {
+    expect(isSerializedBigint(123)).toBeFalsy();
+  });
+
+  it('should return false for a non-serialized bigint value', () => {
+    const value = {
+      __kind: 'not-bigint',
+      value: '1234567890',
+    };
+    expect(isSerializedBigint(value)).toBeFalsy();
+  });
+});
+
+describe('serializeStateObject', () => {
+  it('should serialize a simple object', () => {
+    const object = {
+      a: 1,
+      b: 2,
+      c: 3,
+    };
+    const expectedJson = `{"a":1,"b":2,"c":3}`;
+    expect(serializeStateObject(object)).toEqual(expectedJson);
+  });
+
+  it('should serialize a bigint', () => {
+    const object = {
+      a: 123456789123456789n,
+    };
+    const expectedJson =
+        `{"a":{"__kind":"bigint","value":"123456789123456789"}}`;
+    expect(serializeStateObject(object)).toEqual(expectedJson);
+  });
+
+  it('should not serialize a non-serializable property', () => {
+    const object = {
+      a: 1,
+      b: 2,
+      c: 3,
+      nonSerializableState: 4,
+    };
+    const expectedJson = `{"a":1,"b":2,"c":3}`;
+    expect(serializeStateObject(object)).toEqual(expectedJson);
+  });
+});
+
+describe('deserializeStateObject', () => {
+  it('should deserialize a simple object', () => {
+    const json = `{"a":1,"b":2,"c":3}`;
+    const expectedObject = {
+      a: 1,
+      b: 2,
+      c: 3,
+    };
+    expect(deserializeStateObject(json)).toEqual(expectedObject);
+  });
+
+  it('should deserialize a bigint', () => {
+    const json = `{"a":{"__kind":"bigint","value":"123456789123456789"}}`;
+    const expectedObject = {
+      a: 123456789123456789n,
+    };
+    expect(deserializeStateObject(json)).toEqual(expectedObject);
+  });
+
+  it('should deserialize a null', () => {
+    const json = `{"a":null}`;
+    const expectedObject = {
+      a: null,
+    };
+    expect(deserializeStateObject(json)).toEqual(expectedObject);
+  });
+});
diff --git a/ui/src/controller/aggregation/counter_aggregation_controller.ts b/ui/src/controller/aggregation/counter_aggregation_controller.ts
index dd7f14a..e632f8e 100644
--- a/ui/src/controller/aggregation/counter_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/counter_aggregation_controller.ts
@@ -15,7 +15,7 @@
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
 import {Area, Sorting} from '../../common/state';
-import {toNs} from '../../common/time';
+import {tpDurationToSeconds} from '../../common/time';
 import {globals} from '../../frontend/globals';
 import {Config, COUNTER_TRACK_KIND} from '../../tracks/counter';
 
@@ -38,21 +38,22 @@
       }
     }
     if (ids.length === 0) return false;
+    const duration = area.end - area.start;
+    const durationSec = tpDurationToSeconds(duration);
 
     const query = `create view ${this.kind} as select
     name,
     count(1) as count,
-    round(sum(weighted_value)/${
-        toNs(area.endSec) - toNs(area.startSec)}, 2) as avg_value,
+    round(sum(weighted_value)/${duration}, 2) as avg_value,
     last as last_value,
     first as first_value,
     max(last) - min(first) as delta_value,
-    round((max(last) - min(first))/${area.endSec - area.startSec}, 2) as rate,
+    round((max(last) - min(first))/${durationSec}, 2) as rate,
     min(value) as min_value,
     max(value) as max_value
     from
         (select *,
-        (min(ts + dur, ${toNs(area.endSec)}) - max(ts,${toNs(area.startSec)}))
+        (min(ts + dur, ${area.end}) - max(ts,${area.start}))
         * value as weighted_value,
         first_value(value) over
         (partition by track_id order by ts) as first,
@@ -61,8 +62,8 @@
             range between unbounded preceding and unbounded following) as last
         from experimental_counter_dur
         where track_id in (${ids})
-        and ts + dur >= ${toNs(area.startSec)} and
-        ts <= ${toNs(area.endSec)})
+        and ts + dur >= ${area.start} and
+        ts <= ${area.end})
     join counter_track
     on track_id = counter_track.id
     group by track_id`;
diff --git a/ui/src/controller/aggregation/cpu_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
index 94d3950..452ca75 100644
--- a/ui/src/controller/aggregation/cpu_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_aggregation_controller.ts
@@ -15,7 +15,6 @@
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
 import {Area, Sorting} from '../../common/state';
-import {toNs} from '../../common/time';
 import {globals} from '../../frontend/globals';
 import {Config, CPU_SLICE_TRACK_KIND} from '../../tracks/cpu_slices';
 
@@ -46,8 +45,8 @@
         JOIN thread_state USING(utid)
         WHERE cpu IN (${selectedCpus}) AND
         state = "Running" AND
-        thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND
-        thread_state.ts < ${toNs(area.endSec)} group by utid`;
+        thread_state.ts + thread_state.dur > ${area.start} AND
+        thread_state.ts < ${area.end} group by utid`;
 
     await engine.query(query);
     return true;
diff --git a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
index b28e496..fc24d1e 100644
--- a/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/cpu_by_process_aggregation_controller.ts
@@ -15,7 +15,6 @@
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
 import {Area, Sorting} from '../../common/state';
-import {toNs} from '../../common/time';
 import {globals} from '../../frontend/globals';
 import {Config, CPU_SLICE_TRACK_KIND} from '../../tracks/cpu_slices';
 
@@ -45,8 +44,8 @@
         JOIN thread_state USING(utid)
         WHERE cpu IN (${selectedCpus}) AND
         state = "Running" AND
-        thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND
-        thread_state.ts < ${toNs(area.endSec)} group by upid`;
+        thread_state.ts + thread_state.dur > ${area.start} AND
+        thread_state.ts < ${area.end} group by upid`;
 
     await engine.query(query);
     return true;
diff --git a/ui/src/controller/aggregation/frame_aggregation_controller.ts b/ui/src/controller/aggregation/frame_aggregation_controller.ts
index 97e1f86..a22a83d 100644
--- a/ui/src/controller/aggregation/frame_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/frame_aggregation_controller.ts
@@ -15,7 +15,6 @@
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
 import {Area, Sorting} from '../../common/state';
-import {toNs} from '../../common/time';
 import {globals} from '../../frontend/globals';
 import {
   ACTUAL_FRAMES_SLICE_TRACK_KIND,
@@ -48,8 +47,8 @@
         MAX(dur) as maxDur
         FROM actual_frame_timeline_slice
         WHERE track_id IN (${selectedSqlTrackIds}) AND
-        ts + dur > ${toNs(area.startSec)} AND
-        ts < ${toNs(area.endSec)} group by jank_type`;
+        ts + dur > ${area.start} AND
+        ts < ${area.end} group by jank_type`;
 
     await engine.query(query);
     return true;
diff --git a/ui/src/controller/aggregation/slice_aggregation_controller.ts b/ui/src/controller/aggregation/slice_aggregation_controller.ts
index ebb8000..8dcaccc 100644
--- a/ui/src/controller/aggregation/slice_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/slice_aggregation_controller.ts
@@ -15,7 +15,6 @@
 import {ColumnDef} from '../../common/aggregation_data';
 import {Engine} from '../../common/engine';
 import {Area, Sorting} from '../../common/state';
-import {toNs} from '../../common/time';
 import {globals} from '../../frontend/globals';
 import {
   ASYNC_SLICE_TRACK_KIND,
@@ -64,8 +63,8 @@
         count(1) as occurrences
         FROM slices
         WHERE track_id IN (${selectedTrackIds}) AND
-        ts + dur > ${toNs(area.startSec)} AND
-        ts < ${toNs(area.endSec)} group by name`;
+        ts + dur > ${area.start} AND
+        ts < ${area.end} group by name`;
 
     await engine.query(query);
     return true;
diff --git a/ui/src/controller/aggregation/thread_aggregation_controller.ts b/ui/src/controller/aggregation/thread_aggregation_controller.ts
index ddfadae..6e288d5 100644
--- a/ui/src/controller/aggregation/thread_aggregation_controller.ts
+++ b/ui/src/controller/aggregation/thread_aggregation_controller.ts
@@ -17,7 +17,6 @@
 import {NUM, NUM_NULL, STR_NULL} from '../../common/query_result';
 import {Area, Sorting} from '../../common/state';
 import {translateState} from '../../common/thread_state';
-import {toNs} from '../../common/time';
 import {globals} from '../../frontend/globals';
 import {
   Config,
@@ -60,8 +59,8 @@
       JOIN thread USING(upid)
       JOIN thread_state USING(utid)
       WHERE utid IN (${this.utids}) AND
-      thread_state.ts + thread_state.dur > ${toNs(area.startSec)} AND
-      thread_state.ts < ${toNs(area.endSec)}
+      thread_state.ts + thread_state.dur > ${area.start} AND
+      thread_state.ts < ${area.end}
       GROUP BY utid, concat_state
     `;
 
@@ -78,8 +77,8 @@
       JOIN thread USING(upid)
       JOIN thread_state USING(utid)
       WHERE utid IN (${this.utids}) AND thread_state.ts + thread_state.dur > ${
-            toNs(area.startSec)} AND
-      thread_state.ts < ${toNs(area.endSec)}
+            area.start} AND
+      thread_state.ts < ${area.end}
       GROUP BY state, io_wait`;
     const result = await engine.query(query);
 
diff --git a/ui/src/controller/area_selection_handler.ts b/ui/src/controller/area_selection_handler.ts
index b1d1c7e..32dcb11 100644
--- a/ui/src/controller/area_selection_handler.ts
+++ b/ui/src/controller/area_selection_handler.ts
@@ -36,10 +36,10 @@
       // where `a ||= b` is formatted to `a || = b`, by inserting a space which
       // breaks the operator.
       // Therefore, we are using the pattern `a = a || b` instead.
-      hasAreaChanged = hasAreaChanged ||
-          selectedArea.startSec !== this.previousArea.startSec;
       hasAreaChanged =
-          hasAreaChanged || selectedArea.endSec !== this.previousArea.endSec;
+          hasAreaChanged || selectedArea.start !== this.previousArea.start;
+      hasAreaChanged =
+          hasAreaChanged || selectedArea.end !== this.previousArea.end;
       hasAreaChanged = hasAreaChanged ||
           selectedArea.tracks.length !== this.previousArea.tracks.length;
       for (let i = 0; i < selectedArea.tracks.length; ++i) {
diff --git a/ui/src/controller/area_selection_handler_unittest.ts b/ui/src/controller/area_selection_handler_unittest.ts
index c5a27c0..caac678 100644
--- a/ui/src/controller/area_selection_handler_unittest.ts
+++ b/ui/src/controller/area_selection_handler_unittest.ts
@@ -20,7 +20,7 @@
 
 test('validAreaAfterUndefinedArea', () => {
   const areaId = '0';
-  const latestArea: AreaById = {startSec: 0, endSec: 1, tracks: [], id: areaId};
+  const latestArea: AreaById = {start: 0n, end: 1n, tracks: [], id: areaId};
   globals.state = createEmptyState();
   globals.state.currentSelection = {kind: 'AREA', areaId};
   globals.state.areas[areaId] = latestArea;
@@ -35,7 +35,7 @@
 test('UndefinedAreaAfterValidArea', () => {
   const previousAreaId = '0';
   const previous:
-      AreaById = {startSec: 0, endSec: 1, tracks: [], id: previousAreaId};
+      AreaById = {start: 0n, end: 1n, tracks: [], id: previousAreaId};
   globals.state = createEmptyState();
   globals.state.currentSelection = {
     kind: 'AREA',
@@ -71,7 +71,7 @@
 test('validAreaAfterValidArea', () => {
   const previousAreaId = '0';
   const previous:
-      AreaById = {startSec: 0, endSec: 1, tracks: [], id: previousAreaId};
+      AreaById = {start: 0n, end: 1n, tracks: [], id: previousAreaId};
   globals.state = createEmptyState();
   globals.state.currentSelection = {
     kind: 'AREA',
@@ -82,8 +82,7 @@
   areaSelectionHandler.getAreaChange();
 
   const currentAreaId = '1';
-  const current:
-      AreaById = {startSec: 1, endSec: 2, tracks: [], id: currentAreaId};
+  const current: AreaById = {start: 1n, end: 2n, tracks: [], id: currentAreaId};
   globals.state.currentSelection = {
     kind: 'AREA',
     areaId: currentAreaId,
@@ -98,7 +97,7 @@
 test('sameAreaSelected', () => {
   const previousAreaId = '0';
   const previous:
-      AreaById = {startSec: 0, endSec: 1, tracks: [], id: previousAreaId};
+      AreaById = {start: 0n, end: 1n, tracks: [], id: previousAreaId};
   globals.state = createEmptyState();
   globals.state.currentSelection = {
     kind: 'AREA',
@@ -109,8 +108,7 @@
   areaSelectionHandler.getAreaChange();
 
   const currentAreaId = '0';
-  const current:
-      AreaById = {startSec: 0, endSec: 1, tracks: [], id: currentAreaId};
+  const current: AreaById = {start: 0n, end: 1n, tracks: [], id: currentAreaId};
   globals.state.currentSelection = {
     kind: 'AREA',
     areaId: currentAreaId,
@@ -128,7 +126,7 @@
   areaSelectionHandler.getAreaChange();
 
   globals.state
-      .currentSelection = {kind: 'COUNTER', leftTs: 0, rightTs: 0, id: 1};
+      .currentSelection = {kind: 'COUNTER', leftTs: 0n, rightTs: 0n, id: 1};
   const [hasAreaChanged, selectedArea] = areaSelectionHandler.getAreaChange();
 
   expect(hasAreaChanged).toEqual(false);
diff --git a/ui/src/controller/flamegraph_controller.ts b/ui/src/controller/flamegraph_controller.ts
index e94531d..31a74c3 100644
--- a/ui/src/controller/flamegraph_controller.ts
+++ b/ui/src/controller/flamegraph_controller.ts
@@ -27,7 +27,7 @@
 } from '../common/flamegraph_util';
 import {NUM, STR} from '../common/query_result';
 import {CallsiteInfo, FlamegraphState, ProfileType} from '../common/state';
-import {toNs} from '../common/time';
+import {tpDurationToSeconds, TPTime} from '../common/time';
 import {FlamegraphDetails, globals} from '../frontend/globals';
 import {publishFlamegraphDetails} from '../frontend/publish';
 import {
@@ -145,8 +145,8 @@
       }
       globals.dispatch(Actions.openFlamegraph({
         upids,
-        startNs: toNs(area.startSec),
-        endNs: toNs(area.endSec),
+        start: area.start,
+        end: area.end,
         type: ProfileType.PERF_SAMPLE,
         viewingOption: PERF_SAMPLES_KEY,
       }));
@@ -169,8 +169,8 @@
     const selectedFlamegraphState = {...selection};
     const flamegraphMetadata = await this.getFlamegraphMetadata(
         selection.type,
-        selectedFlamegraphState.startNs,
-        selectedFlamegraphState.endNs,
+        selectedFlamegraphState.start,
+        selectedFlamegraphState.end,
         selectedFlamegraphState.upids);
     if (flamegraphMetadata !== undefined) {
       Object.assign(this.flamegraphDetails, flamegraphMetadata);
@@ -192,7 +192,7 @@
         selectedFlamegraphState.expandedCallsite.totalSize;
 
     const key = `${selectedFlamegraphState.upids};${
-        selectedFlamegraphState.startNs};${selectedFlamegraphState.endNs}`;
+        selectedFlamegraphState.start};${selectedFlamegraphState.end}`;
 
     try {
       const flamegraphData = await this.getFlamegraphData(
@@ -200,15 +200,15 @@
           selectedFlamegraphState.viewingOption ?
               selectedFlamegraphState.viewingOption :
               DEFAULT_VIEWING_OPTION,
-          selection.startNs,
-          selection.endNs,
+          selection.start,
+          selection.end,
           selectedFlamegraphState.upids,
           selectedFlamegraphState.type,
           selectedFlamegraphState.focusRegex);
       if (flamegraphData !== undefined && selection &&
           selection.kind === selectedFlamegraphState.kind &&
-          selection.startNs === selectedFlamegraphState.startNs &&
-          selection.endNs === selectedFlamegraphState.endNs) {
+          selection.start === selectedFlamegraphState.start &&
+          selection.end === selectedFlamegraphState.end) {
         const expandedFlamegraphData =
             expandCallsites(flamegraphData, expandedId);
         this.prepareAndMergeCallsites(
@@ -230,8 +230,8 @@
   private shouldRequestData(selection: FlamegraphState) {
     return selection.kind === 'FLAMEGRAPH_STATE' &&
         (this.lastSelectedFlamegraphState === undefined ||
-         (this.lastSelectedFlamegraphState.startNs !== selection.startNs ||
-          this.lastSelectedFlamegraphState.endNs !== selection.endNs ||
+         (this.lastSelectedFlamegraphState.start !== selection.start ||
+          this.lastSelectedFlamegraphState.end !== selection.end ||
           this.lastSelectedFlamegraphState.type !== selection.type ||
           !FlamegraphController.areArraysEqual(
               this.lastSelectedFlamegraphState.upids, selection.upids) ||
@@ -267,7 +267,7 @@
   }
 
   async getFlamegraphData(
-      baseKey: string, viewingOption: string, startNs: number, endNs: number,
+      baseKey: string, viewingOption: string, start: TPTime, end: TPTime,
       upids: number[], type: ProfileType,
       focusRegex: string): Promise<CallsiteInfo[]> {
     let currentData: CallsiteInfo[];
@@ -280,8 +280,8 @@
       // Collecting data for drawing flamegraph for selected profile.
       // Data needs to be in following format:
       // id, name, parent_id, depth, total_size
-      const tableName = await this.prepareViewsAndTables(
-          startNs, endNs, upids, type, focusRegex);
+      const tableName =
+          await this.prepareViewsAndTables(start, end, upids, type, focusRegex);
       currentData = await this.getFlamegraphDataFromTables(
           tableName, viewingOption, focusRegex);
       this.flamegraphDatasets.set(key, currentData);
@@ -413,7 +413,7 @@
   }
 
   private async prepareViewsAndTables(
-      startNs: number, endNs: number, upids: number[], type: ProfileType,
+      start: TPTime, end: TPTime, upids: number[], type: ProfileType,
       focusRegex: string): Promise<string> {
     // Creating unique names for views so we can reuse and not delete them
     // for each marker.
@@ -437,8 +437,8 @@
           cumulative_alloc_size, cumulative_count, cumulative_alloc_count,
           size, alloc_size, count, alloc_count, source_file, line_number
           from experimental_flamegraph
-          where profile_type = '${flamegraphType}' and ${startNs} <= ts and
-              ts <= ${endNs} and ${upidConditional}
+          where profile_type = '${flamegraphType}' and ${start} <= ts and
+              ts <= ${end} and ${upidConditional}
           ${focusRegexConditional}`);
     }
     return this.cache.getTableName(
@@ -447,7 +447,7 @@
           size, alloc_size, count, alloc_count, source_file, line_number
           from experimental_flamegraph
           where profile_type = '${flamegraphType}'
-            and ts = ${endNs}
+            and ts = ${end}
             and upid = ${upids[0]}
             ${focusRegexConditional}`);
   }
@@ -455,7 +455,8 @@
   getMinSizeDisplayed(flamegraphData: CallsiteInfo[], rootSize?: number):
       number {
     const timeState = globals.state.frontendLocalState.visibleState;
-    let width = (timeState.endSec - timeState.startSec) / timeState.resolution;
+    const dur = globals.stateVisibleTime().duration;
+    let width = tpDurationToSeconds(dur / timeState.resolution);
     // TODO(168048193): Remove screen size hack:
     width = Math.max(width, 800);
     if (rootSize === undefined) {
@@ -465,11 +466,12 @@
   }
 
   async getFlamegraphMetadata(
-      type: ProfileType, startNs: number, endNs: number, upids: number[]) {
+      type: ProfileType, start: TPTime, end: TPTime,
+      upids: number[]): Promise<FlamegraphDetails|undefined> {
     // Don't do anything if selection of the marker stayed the same.
     if ((this.lastSelectedFlamegraphState !== undefined &&
-         ((this.lastSelectedFlamegraphState.startNs === startNs &&
-           this.lastSelectedFlamegraphState.endNs === endNs &&
+         ((this.lastSelectedFlamegraphState.start === start &&
+           this.lastSelectedFlamegraphState.end === end &&
            FlamegraphController.areArraysEqual(
                this.lastSelectedFlamegraphState.upids, upids))))) {
       return undefined;
@@ -486,7 +488,7 @@
     for (let i = 0; it.valid(); ++i, it.next()) {
       pids.push(it.pid);
     }
-    return {startNs, durNs: endNs - startNs, pids, upids, type};
+    return {start, dur: end - start, pids, upids, type};
   }
 
   private static areArraysEqual(a: number[], b: number[]) {
diff --git a/ui/src/controller/flow_events_controller.ts b/ui/src/controller/flow_events_controller.ts
index d89b514..07125e1 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -16,7 +16,7 @@
 import {featureFlags} from '../common/feature_flags';
 import {NUM, STR_NULL} from '../common/query_result';
 import {Area} from '../common/state';
-import {fromNs, toNs} from '../common/time';
+import {fromNs} from '../common/time';
 import {Flow, globals} from '../frontend/globals';
 import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish';
 import {
@@ -241,8 +241,8 @@
     const area = globals.state.areas[areaId];
     if (this.lastSelectedKind === 'AREA' && this.lastSelectedArea &&
         this.lastSelectedArea.tracks.join(',') === area.tracks.join(',') &&
-        this.lastSelectedArea.endSec === area.endSec &&
-        this.lastSelectedArea.startSec === area.startSec) {
+        this.lastSelectedArea.end === area.end &&
+        this.lastSelectedArea.start === area.start) {
       return;
     }
 
@@ -268,8 +268,8 @@
 
     const tracks = `(${trackIds.join(',')})`;
 
-    const startNs = toNs(area.startSec);
-    const endNs = toNs(area.endSec);
+    const startNs = area.start;
+    const endNs = area.end;
 
     const query = `
     select
diff --git a/ui/src/controller/ftrace_controller.ts b/ui/src/controller/ftrace_controller.ts
index 132eda2..a071ec5 100644
--- a/ui/src/controller/ftrace_controller.ts
+++ b/ui/src/controller/ftrace_controller.ts
@@ -12,13 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {FtraceFilterState} from 'src/common/state';
-
 import {Engine} from '../common/engine';
-import {NUM, STR, STR_NULL} from '../common/query_result';
-import {TimeSpan, toNsCeil, toNsFloor} from '../common/time';
-import {FtraceEvent, globals as frontendGlobals} from '../frontend/globals';
-import {globals} from '../frontend/globals';
+import {
+  HighPrecisionTime,
+  HighPrecisionTimeSpan,
+} from '../common/high_precision_time';
+import {LONG, NUM, STR, STR_NULL} from '../common/query_result';
+import {FtraceFilterState, Pagination} from '../common/state';
+import {Span} from '../common/time';
+import {FtraceEvent, globals} from '../frontend/globals';
 import {publishFtracePanelData} from '../frontend/publish';
 import {ratelimit} from '../frontend/rate_limiters';
 
@@ -34,30 +36,12 @@
   numEvents: number;
 }
 
-function cloneFtraceFilterState(other: FtraceFilterState): FtraceFilterState {
-  return {
-    excludedNames: [...other.excludedNames],
-  };
-}
-
-function ftraceFilterStateEq(
-    a: FtraceFilterState, b: FtraceFilterState): boolean {
-  if (a.excludedNames === b.excludedNames) return true;
-  if (a.excludedNames.length !== b.excludedNames.length) return false;
-
-  for (let i = 0; i < a.excludedNames.length; ++i) {
-    if (a.excludedNames[i] !== b.excludedNames[i]) return false;
-  }
-
-  return true;
-}
-
 export class FtraceController extends Controller<'main'> {
   private engine: Engine;
-  private oldSpan: TimeSpan = new TimeSpan(0, 0);
-  private oldFtraceFilter: FtraceFilterState = {
-    excludedNames: [],
-  };
+  private oldSpan: Span<HighPrecisionTime> = HighPrecisionTimeSpan.ZERO;
+  private oldFtraceFilter?: FtraceFilterState;
+  private oldPagination?: Pagination;
+
   constructor({engine}: FtraceControllerArgs) {
     super('main');
     this.engine = engine;
@@ -65,44 +49,38 @@
 
   run() {
     if (this.shouldUpdate()) {
-      this.updateEverything();
+      this.oldSpan = globals.frontendLocalState.visibleWindowTime;
+      this.oldFtraceFilter = globals.state.ftraceFilter;
+      this.oldPagination = globals.state.ftracePagination;
+      if (globals.state.ftracePagination.count > 0) {
+        this.lookupFtraceEventsRateLimited();
+      }
     }
   }
 
-  private updateEverything = ratelimit(() => {
+  private lookupFtraceEventsRateLimited = ratelimit(() => {
     const {offset, count} = globals.state.ftracePagination;
-    this.oldSpan = frontendGlobals.frontendLocalState.visibleWindowTime;
-    this.oldFtraceFilter =
-        cloneFtraceFilterState(frontendGlobals.state.ftraceFilter);
-    this.lookupFtraceEvents(offset, count).then(({events,
-                                                  offset,
-                                                  numEvents}: RetVal) => {
+    // The formatter doesn't like formatted chained methods :(
+    const promise = this.lookupFtraceEvents(offset, count);
+    promise.then(({events, offset, numEvents}: RetVal) => {
       publishFtracePanelData({events, offset, numEvents});
     });
   }, 250);
 
   private shouldUpdate(): boolean {
-    if (this.oldSpan != frontendGlobals.frontendLocalState.visibleWindowTime) {
-      // The visible window has changed, definitely update
+    // Has the visible window moved?
+    const visibleWindow = globals.frontendLocalState.visibleWindowTime;
+    if (!this.oldSpan.equals(visibleWindow)) {
       return true;
     }
 
-    const globalPanelData = frontendGlobals.ftracePanelData;
-    if (!globalPanelData) {
-      // No state has been written yet, so we definitely need to update
+    // Has the pagination changed?
+    if (this.oldPagination !== globals.state.ftracePagination) {
       return true;
     }
 
-    // Work out whether we've scrolled near our rendered bounds
-    const {offset, count} = globals.state.ftracePagination;
-    if (offset != globalPanelData.offset ||
-        count != globalPanelData.events.length) {
-      return true;
-    }
-
-    // Work out of the ftrace filter has changed
-    const filter = frontendGlobals.state.ftraceFilter;
-    if (!ftraceFilterStateEq(this.oldFtraceFilter, filter)) {
+    // Has the filter changed?
+    if (this.oldFtraceFilter !== globals.state.ftraceFilter) {
       return true;
     }
 
@@ -110,16 +88,21 @@
   }
 
   async lookupFtraceEvents(offset: number, count: number): Promise<RetVal> {
-    const appState = frontendGlobals.state;
-    const frontendState = frontendGlobals.frontendLocalState;
+    const appState = globals.state;
+    const frontendState = globals.frontendLocalState;
     const {start, end} = frontendState.visibleWindowTime;
 
-    const startNs = toNsFloor(start);
-    const endNs = toNsCeil(end);
+    const startNs = start.nanos;
+    const endNs = end.nanos;
 
     const excludeList = appState.ftraceFilter.excludedNames;
     const excludeListSql = excludeList.map((s) => `'${s}'`).join(',');
 
+    // TODO(stevegolton): This query can be slow when traces are huge.
+    // The number of events is only used for correctly sizing the panel's
+    // scroll container so that the scrollbar works as if the panel were fully
+    // populated.
+    // Perhaps we could work out some UX that doesn't need this.
     let queryRes = await this.engine.query(`
       select count(id) as numEvents
       from ftrace_event
@@ -152,7 +135,7 @@
     const it = queryRes.iter(
         {
           id: NUM,
-          ts: NUM,
+          ts: LONG,
           name: STR,
           cpu: NUM,
           thread: STR_NULL,
diff --git a/ui/src/controller/logs_controller.ts b/ui/src/controller/logs_controller.ts
index d8f9b3a..c931ce0 100644
--- a/ui/src/controller/logs_controller.ts
+++ b/ui/src/controller/logs_controller.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../base/bigint_math';
 import {Engine} from '../common/engine';
 import {
   LogBounds,
@@ -20,62 +21,61 @@
   LogEntriesKey,
   LogExistsKey,
 } from '../common/logs';
-import {NUM, STR} from '../common/query_result';
+import {LONG, LONG_NULL, NUM, STR} from '../common/query_result';
 import {escapeGlob, escapeQuery} from '../common/query_utils';
 import {LogFilteringCriteria} from '../common/state';
-import {fromNs, TimeSpan, toNsCeil, toNsFloor} from '../common/time';
+import {Span} from '../common/time';
+import {
+  TPTime,
+  TPTimeSpan,
+} from '../common/time';
 import {globals} from '../frontend/globals';
 import {publishTrackData} from '../frontend/publish';
 
 import {Controller} from './controller';
 
 async function updateLogBounds(
-    engine: Engine, span: TimeSpan): Promise<LogBounds> {
-  const vizStartNs = toNsFloor(span.start);
-  const vizEndNs = toNsCeil(span.end);
+    engine: Engine, span: Span<TPTime>): Promise<LogBounds> {
+  const vizStartNs = span.start;
+  const vizEndNs = span.end;
 
-  const countResult = await engine.query(`select
-      ifnull(min(ts), 0) as minTs,
-      ifnull(max(ts), 0) as maxTs,
-      count(ts) as countTs
-     from filtered_logs
-        where ts >= ${vizStartNs}
-        and ts <= ${vizEndNs}`);
+  const vizFilter = `ts between ${vizStartNs} and ${vizEndNs}`;
 
-  const countRow = countResult.firstRow({minTs: NUM, maxTs: NUM, countTs: NUM});
+  const result = await engine.query(`select
+      min(ts) as minTs,
+      max(ts) as maxTs,
+      min(case when ${vizFilter} then ts end) as minVizTs,
+      max(case when ${vizFilter} then ts end) as maxVizTs,
+      count(case when ${vizFilter} then ts end) as countTs
+    from filtered_logs`);
 
-  const firstRowNs = countRow.minTs;
-  const lastRowNs = countRow.maxTs;
-  const total = countRow.countTs;
+  const data = result.firstRow({
+    minTs: LONG_NULL,
+    maxTs: LONG_NULL,
+    minVizTs: LONG_NULL,
+    maxVizTs: LONG_NULL,
+    countTs: NUM,
+  });
 
-  const minResult = await engine.query(`
-     select ifnull(max(ts), 0) as maxTs from filtered_logs where ts < ${
-      vizStartNs}`);
-  const startNs = minResult.firstRow({maxTs: NUM}).maxTs;
+  const firstLogTs = data.minTs ?? 0n;
+  const lastLogTs = data.maxTs ?? BigintMath.INT64_MAX;
 
-  const maxResult = await engine.query(`
-     select ifnull(min(ts), 0) as minTs from filtered_logs where ts > ${
-      vizEndNs}`);
-  const endNs = maxResult.firstRow({minTs: NUM}).minTs;
-
-  const startTs = startNs ? fromNs(startNs) : 0;
-  const endTs = endNs ? fromNs(endNs) : Number.MAX_SAFE_INTEGER;
-  const firstRowTs = firstRowNs ? fromNs(firstRowNs) : endTs;
-  const lastRowTs = lastRowNs ? fromNs(lastRowNs) : startTs;
-  return {
-    startTs,
-    endTs,
-    firstRowTs,
-    lastRowTs,
-    total,
+  const bounds: LogBounds = {
+    firstLogTs,
+    lastLogTs,
+    firstVisibleLogTs: data.minVizTs ?? firstLogTs,
+    lastVisibleLogTs: data.maxVizTs ?? lastLogTs,
+    totalVisibleLogs: data.countTs,
   };
+
+  return bounds;
 }
 
 async function updateLogEntries(
-    engine: Engine, span: TimeSpan, pagination: Pagination):
+    engine: Engine, span: Span<TPTime>, pagination: Pagination):
     Promise<LogEntries> {
-  const vizStartNs = toNsFloor(span.start);
-  const vizEndNs = toNsCeil(span.end);
+  const vizStartNs = span.start;
+  const vizEndNs = span.end;
   const vizSqlBounds = `ts >= ${vizStartNs} and ts <= ${vizEndNs}`;
 
   const rowsResult = await engine.query(`
@@ -101,7 +101,7 @@
   const processName = [];
 
   const it = rowsResult.iter({
-    ts: NUM,
+    ts: LONG,
     prio: NUM,
     tag: STR,
     msg: STR,
@@ -179,7 +179,7 @@
  */
 export class LogsController extends Controller<'main'> {
   private engine: Engine;
-  private span: TimeSpan;
+  private span: Span<TPTime>;
   private pagination: Pagination;
   private hasLogs = false;
   private logFilteringCriteria?: LogFilteringCriteria;
@@ -189,7 +189,7 @@
   constructor(args: LogsControllerArgs) {
     super('main');
     this.engine = args.engine;
-    this.span = new TimeSpan(0, 10);
+    this.span = new TPTimeSpan(0n, BigInt(10e9));
     this.pagination = new Pagination(0, 0);
     this.hasAnyLogs().then((exists) => {
       this.hasLogs = exists;
@@ -226,8 +226,7 @@
   }
 
   private async updateLogTracks() {
-    const traceTime = globals.state.frontendLocalState.visibleState;
-    const newSpan = new TimeSpan(traceTime.startSec, traceTime.endSec);
+    const newSpan = globals.stateVisibleTime();
     const oldSpan = this.span;
 
     const pagination = globals.state.logsPagination;
diff --git a/ui/src/controller/permalink_controller.ts b/ui/src/controller/permalink_controller.ts
index a382a40..ad58b27 100644
--- a/ui/src/controller/permalink_controller.ts
+++ b/ui/src/controller/permalink_controller.ts
@@ -26,6 +26,7 @@
 import {
   BUCKET_NAME,
   buggyToSha256,
+  deserializeStateObject,
   saveState,
   saveTrace,
   toSha256,
@@ -195,7 +196,7 @@
     }
     const text = await response.text();
     const stateHash = await toSha256(text);
-    const state = JSON.parse(text);
+    const state = deserializeStateObject(text);
     if (stateHash !== id) {
       // Old permalinks incorrectly dropped some digits from the
       // hexdigest of the SHA256. We don't want to invalidate those
diff --git a/ui/src/controller/record_controller.ts b/ui/src/controller/record_controller.ts
index 598c85e..5982af5 100644
--- a/ui/src/controller/record_controller.ts
+++ b/ui/src/controller/record_controller.ts
@@ -128,7 +128,8 @@
     return value.startsWith('MEMINFO_') || value.startsWith('VMSTAT_') ||
         value.startsWith('STAT_') || value.startsWith('LID_') ||
         value.startsWith('BATTERY_COUNTER_') || value === 'DISCARD' ||
-        value === 'RING_BUFFER';
+        value === 'RING_BUFFER' || value === 'BACKGROUND' ||
+        value === 'USER_INITIATED';
   }
   // Since javascript doesn't have 64 bit numbers when converting protos to
   // json the proto library encodes them as strings. This is lossy since
@@ -139,9 +140,10 @@
   function is64BitNumber(key: string): boolean {
     return [
       'maxFileSizeBytes',
+      'pid',
       'samplingIntervalBytes',
       'shmemSizeBytes',
-      'pid',
+      'timestampUnitMultiplier',
     ].includes(key);
   }
   function* message(msg: {}, indent: number): IterableIterator<string> {
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index d1df094..c5968c2 100644
--- a/ui/src/controller/search_controller.ts
+++ b/ui/src/controller/search_controller.ts
@@ -12,13 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../base/bigint_math';
 import {sqliteString} from '../base/string_utils';
 import {Engine} from '../common/engine';
 import {NUM, STR} from '../common/query_result';
 import {escapeSearchQuery} from '../common/query_utils';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
-import {TimeSpan} from '../common/time';
-import {toNs} from '../common/time';
+import {Span} from '../common/time';
+import {
+  TPDuration,
+  TPTime,
+  TPTimeSpan,
+} from '../common/time';
 import {globals} from '../frontend/globals';
 import {publishSearch, publishSearchResult} from '../frontend/publish';
 
@@ -30,8 +35,8 @@
 
 export class SearchController extends Controller<'main'> {
   private engine: Engine;
-  private previousSpan: TimeSpan;
-  private previousResolution: number;
+  private previousSpan: Span<TPTime>;
+  private previousResolution: TPDuration;
   private previousSearch: string;
   private updateInProgress: boolean;
   private setupInProgress: boolean;
@@ -39,11 +44,11 @@
   constructor(args: SearchControllerArgs) {
     super('main');
     this.engine = args.engine;
-    this.previousSpan = new TimeSpan(0, 1);
+    this.previousSpan = new TPTimeSpan(0n, 1n);
     this.previousSearch = '';
     this.updateInProgress = false;
     this.setupInProgress = true;
-    this.previousResolution = 1;
+    this.previousResolution = 1n;
     this.setup().finally(() => {
       this.setupInProgress = false;
       this.run();
@@ -70,9 +75,9 @@
         omniboxState.mode === 'COMMAND') {
       return;
     }
-    const newSpan = new TimeSpan(visibleState.startSec, visibleState.endSec);
+    const newSpan = globals.stateVisibleTime();
     const newSearch = omniboxState.omnibox;
-    let newResolution = visibleState.resolution;
+    const newResolution = visibleState.resolution;
     if (this.previousSpan.contains(newSpan) &&
         this.previousResolution === newResolution &&
         newSearch === this.previousSearch) {
@@ -83,9 +88,8 @@
     // TODO(hjd): We should restrict this to the start of the trace but
     // that is not easily available here.
     // N.B. Timestamps can be negative.
-    const start = newSpan.start - newSpan.duration;
-    const end = newSpan.end + newSpan.duration;
-    this.previousSpan = new TimeSpan(start, end);
+    const {start, end} = newSpan.pad(newSpan.duration);
+    this.previousSpan = new TPTimeSpan(start, end);
     this.previousResolution = newResolution;
     this.previousSearch = newSearch;
     if (newSearch === '' || newSearch.length < 4) {
@@ -105,25 +109,12 @@
       return;
     }
 
-    let startNs = toNs(newSpan.start);
-    let endNs = toNs(newSpan.end);
-
-    // TODO(hjd): We shouldn't need to be so defensive here:
-    if (!Number.isFinite(startNs)) {
-      startNs = 0;
-    }
-    if (!Number.isFinite(endNs)) {
-      endNs = 1;
-    }
-    if (!Number.isFinite(newResolution)) {
-      newResolution = 1;
-    }
-
     this.updateInProgress = true;
-    const computeSummary = this.update(newSearch, startNs, endNs, newResolution)
-                               .then((summary) => {
-                                 publishSearch(summary);
-                               });
+    const computeSummary =
+        this.update(newSearch, newSpan.start, newSpan.end, newResolution)
+            .then((summary) => {
+              publishSearch(summary);
+            });
 
     const computeResults =
         this.specificSearch(newSearch).then((searchResults) => {
@@ -140,15 +131,14 @@
   onDestroy() {}
 
   private async update(
-      search: string, startNs: number, endNs: number,
-      resolution: number): Promise<SearchSummary> {
-    const quantumNs = Math.round(resolution * 10 * 1e9);
-
+      search: string, startNs: TPTime, endNs: TPTime,
+      resolution: TPDuration): Promise<SearchSummary> {
     const searchLiteral = escapeSearchQuery(search);
 
-    startNs = Math.floor(startNs / quantumNs) * quantumNs;
+    const quantumNs = resolution * 10n;
+    startNs = BigintMath.quantizeFloor(startNs, quantumNs);
 
-    const windowDur = Math.max(endNs - startNs, 1);
+    const windowDur = BigintMath.max(endNs - startNs, 1n);
     await this.query(`update search_summary_window set
       window_start=${startNs},
       window_dur=${windowDur},
diff --git a/ui/src/controller/selection_controller.ts b/ui/src/controller/selection_controller.ts
index 66cf3d0..b5a3907 100644
--- a/ui/src/controller/selection_controller.ts
+++ b/ui/src/controller/selection_controller.ts
@@ -16,14 +16,23 @@
 import {Arg, Args} from '../common/arg_types';
 import {Engine} from '../common/engine';
 import {
+  LONG,
   NUM,
   NUM_NULL,
   STR,
   STR_NULL,
 } from '../common/query_result';
 import {ChromeSliceSelection} from '../common/state';
-import {fromNs, toNs} from '../common/time';
-import {SliceDetails, ThreadStateDetails} from '../frontend/globals';
+import {
+  tpDurationFromSql,
+  TPTime,
+  tpTimeFromSql,
+} from '../common/time';
+import {
+  CounterDetails,
+  SliceDetails,
+  ThreadStateDetails,
+} from '../frontend/globals';
 import {globals} from '../frontend/globals';
 import {
   publishCounterDetails,
@@ -176,10 +185,10 @@
         case 'id':
           break;
         case 'ts':
-          ts = fromNs(Number(v)) - globals.state.traceTime.startSec;
+          ts = tpTimeFromSql(v);
           break;
         case 'thread_ts':
-          threadTs = fromNs(Number(v));
+          threadTs = tpTimeFromSql(v);
           break;
         case 'absTime':
           if (v) absTime = `${v}`;
@@ -188,10 +197,10 @@
           name = `${v}`;
           break;
         case 'dur':
-          dur = fromNs(Number(v));
+          dur = tpDurationFromSql(v);
           break;
         case 'thread_dur':
-          threadDur = fromNs(Number(v));
+          threadDur = tpDurationFromSql(v);
           break;
         case 'category':
         case 'cat':
@@ -326,13 +335,13 @@
     const selection = globals.state.currentSelection;
     if (result.numRows() > 0 && selection) {
       const row = result.firstRow({
-        ts: NUM,
-        dur: NUM,
+        ts: LONG,
+        dur: LONG,
       });
-      const ts = row.ts;
-      const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
-      const dur = fromNs(row.dur);
-      const selected: ThreadStateDetails = {ts: timeFromStart, dur};
+      const selected: ThreadStateDetails = {
+        ts: row.ts,
+        dur: row.dur,
+      };
       publishThreadStateDetails(selected);
     }
   }
@@ -353,8 +362,8 @@
     const selection = globals.state.currentSelection;
     if (result.numRows() > 0 && selection) {
       const row = result.firstRow({
-        ts: NUM,
-        dur: NUM,
+        ts: LONG,
+        dur: LONG,
         priority: NUM,
         endState: STR_NULL,
         utid: NUM,
@@ -362,15 +371,14 @@
         threadStateId: NUM_NULL,
       });
       const ts = row.ts;
-      const timeFromStart = fromNs(ts) - globals.state.traceTime.startSec;
-      const dur = fromNs(row.dur);
+      const dur = row.dur;
       const priority = row.priority;
       const endState = row.endState;
       const utid = row.utid;
       const cpu = row.cpu;
       const threadStateId = row.threadStateId || undefined;
       const selected: SliceDetails = {
-        ts: timeFromStart,
+        ts,
         dur,
         priority,
         endState,
@@ -391,7 +399,8 @@
     }
   }
 
-  async counterDetails(ts: number, rightTs: number, id: number) {
+  async counterDetails(ts: TPTime, rightTs: TPTime, id: number):
+      Promise<CounterDetails> {
     const counter = await this.args.engine.query(
         `SELECT value, track_id as trackId FROM counter WHERE id = ${id}`);
     const row = counter.iter({
@@ -407,17 +416,15 @@
           IFNULL(value, 0) as value
         FROM counter WHERE ts < ${ts} and track_id = ${trackId}`);
     const previousValue = previous.firstRow({value: NUM}).value;
-    const endTs =
-        rightTs !== -1 ? rightTs : toNs(globals.state.traceTime.endSec);
+    const endTs = rightTs !== -1n ? rightTs : globals.state.traceTime.end;
     const delta = value - previousValue;
     const duration = endTs - ts;
-    const startTime = fromNs(ts) - globals.state.traceTime.startSec;
     const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
     const name = uiTrackId ? globals.state.tracks[uiTrackId].name : undefined;
-    return {startTime, value, delta, duration, name};
+    return {startTime: ts, value, delta, duration, name};
   }
 
-  async schedulingDetails(ts: number, utid: number|Long) {
+  async schedulingDetails(ts: TPTime, utid: number|Long) {
     // Find the ts of the first wakeup before the current slice.
     const wakeResult = await this.args.engine.query(`
       select ts, waker_utid as wakerUtid
@@ -430,7 +437,7 @@
       return undefined;
     }
 
-    const wakeFirstRow = wakeResult.firstRow({ts: NUM, wakerUtid: NUM_NULL});
+    const wakeFirstRow = wakeResult.firstRow({ts: LONG, wakerUtid: NUM_NULL});
     const wakeupTs = wakeFirstRow.ts;
     const wakerUtid = wakeFirstRow.wakerUtid;
     if (wakerUtid === null) {
@@ -449,7 +456,7 @@
     // If this is the first sched slice for this utid or if the wakeup found
     // was after the previous slice then we know the wakeup was for this slice.
     if (prevSchedResult.numRows() !== 0 &&
-        wakeupTs < prevSchedResult.firstRow({ts: NUM}).ts) {
+        wakeupTs < prevSchedResult.firstRow({ts: LONG}).ts) {
       return undefined;
     }
 
@@ -468,7 +475,7 @@
     }
 
     const wakerRow = wakerResult.firstRow({cpu: NUM});
-    return {wakeupTs: fromNs(wakeupTs), wakerUtid, wakerCpu: wakerRow.cpu};
+    return {wakeupTs, wakerUtid, wakerCpu: wakerRow.cpu};
   }
 
   async computeThreadDetails(utid: number):
diff --git a/ui/src/controller/trace_controller.ts b/ui/src/controller/trace_controller.ts
index f57105d..f0e2d02 100644
--- a/ui/src/controller/trace_controller.ts
+++ b/ui/src/controller/trace_controller.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../base/bigint_math';
 import {assertExists, assertTrue} from '../base/logging';
 import {
   Actions,
@@ -20,15 +21,30 @@
 import {cacheTrace} from '../common/cache_manager';
 import {Engine} from '../common/engine';
 import {featureFlags, Flag, PERF_SAMPLE_FLAG} from '../common/feature_flags';
+import {
+  HighPrecisionTime,
+  HighPrecisionTimeSpan,
+} from '../common/high_precision_time';
 import {HttpRpcEngine} from '../common/http_rpc_engine';
 import {
   getEnabledMetatracingCategories,
   isMetatracingEnabled,
 } from '../common/metatracing';
-import {NUM, NUM_NULL, QueryError, STR, STR_NULL} from '../common/query_result';
+import {
+  LONG,
+  NUM,
+  NUM_NULL,
+  QueryError,
+  STR,
+  STR_NULL,
+} from '../common/query_result';
 import {onSelectionChanged} from '../common/selection_observer';
 import {defaultTraceTime, EngineMode, ProfileType} from '../common/state';
-import {TimeSpan, toNs, toNsCeil, toNsFloor} from '../common/time';
+import {Span} from '../common/time';
+import {
+  TPTime,
+  TPTimeSpan,
+} from '../common/time';
 import {resetEngineWorker, WasmEngineProxy} from '../common/wasm_engine_proxy';
 import {BottomTabList} from '../frontend/bottom_tab';
 import {
@@ -39,6 +55,7 @@
 } from '../frontend/globals';
 import {showModal} from '../frontend/modal';
 import {
+  clearOverviewData,
   publishFtraceCounters,
   publishMetricError,
   publishOverviewData,
@@ -113,7 +130,6 @@
   'android_dma_heap',
   'android_surfaceflinger',
   'android_batt',
-  'android_camera',
   'android_other_traces',
   'chrome_dropped_frames',
   'chrome_long_latency',
@@ -416,11 +432,11 @@
     const traceUuid = await this.cacheCurrentTrace();
 
     const traceTime = await this.engine.getTraceTimeBounds();
-    const startSec = traceTime.start;
-    const endSec = traceTime.end;
+    const start = traceTime.start;
+    const end = traceTime.end;
     const traceTimeState = {
-      startSec,
-      endSec,
+      start,
+      end,
     };
 
     const shownJsonWarning =
@@ -453,16 +469,16 @@
       Actions.setTraceTime(traceTimeState),
     ];
 
-    const [startVisibleTime, endVisibleTime] =
-      await computeVisibleTime(startSec, endSec, isJsonTrace, this.engine);
+    const visibleTimeSpan = await computeVisibleTime(
+        traceTime.start, traceTime.end, isJsonTrace, this.engine);
     // We don't know the resolution at this point. However this will be
     // replaced in 50ms so a guess is fine.
-    const resolution = (endVisibleTime - startVisibleTime) / 1000;
+    const resolution = visibleTimeSpan.duration.divide(1000).toTPTime();
     actions.push(Actions.setVisibleTraceTime({
-      startSec: startVisibleTime,
-      endSec: endVisibleTime,
+      start: visibleTimeSpan.start.toTPTime(),
+      end: visibleTimeSpan.end.toTPTime(),
       lastUpdate: Date.now() / 1000,
-      resolution,
+      resolution: BigintMath.max(resolution, 1n),
     }));
 
     globals.dispatchMultiple(actions);
@@ -486,7 +502,7 @@
       // Pull out the counts ftrace events by name
       const query = `select
             name,
-            count(*) as cnt
+            count(name) as cnt
           from ftrace_event
           group by name
           order by cnt desc`;
@@ -540,8 +556,8 @@
     if (profile.numRows() !== 1) return;
     const row = profile.firstRow({upid: NUM});
     const upid = row.upid;
-    const leftTs = toNs(globals.state.traceTime.startSec);
-    const rightTs = toNs(globals.state.traceTime.endSec);
+    const leftTs = globals.state.traceTime.start;
+    const rightTs = globals.state.traceTime.end;
     globals.dispatch(Actions.selectPerfSamples(
         {id: 0, upid, leftTs, rightTs, type: ProfileType.PERF_SAMPLE}));
   }
@@ -560,7 +576,7 @@
       order by ts limit 1`;
     const profile = await assertExists(this.engine).query(query);
     if (profile.numRows() !== 1) return;
-    const row = profile.firstRow({ts: NUM, type: STR, upid: NUM});
+    const row = profile.firstRow({ts: LONG, type: STR, upid: NUM});
     const ts = row.ts;
     const type = profileType(row.type);
     const upid = row.upid;
@@ -610,31 +626,32 @@
     publishThreads(threads);
   }
 
-  private async loadTimelineOverview(traceTime: TimeSpan) {
+  private async loadTimelineOverview(trace: Span<TPTime>) {
+    clearOverviewData();
+
     const engine = assertExists<Engine>(this.engine);
-    const numSteps = 100;
-    const stepSec = traceTime.duration / numSteps;
+    const stepSize = BigintMath.max(1n, trace.duration / 100n);
     let hasSchedOverview = false;
-    for (let step = 0; step < numSteps; step++) {
+    for (let start = trace.start; start < trace.end; start += stepSize) {
+      const progress = start - trace.start;
+      const ratio = Number(progress) / Number(trace.duration);
       this.updateStatus(
-        'Loading overview ' +
-        `${Math.round((step + 1) / numSteps * 1000) / 10}%`);
-      const startSec = traceTime.start + step * stepSec;
-      const startNs = toNsFloor(startSec);
-      const endSec = startSec + stepSec;
-      const endNs = toNsCeil(endSec);
+          'Loading overview ' +
+          `${Math.round(ratio * 100)}%`);
+      const end = start + stepSize;
 
       // Sched overview.
       const schedResult = await engine.query(
-        `select sum(dur)/${stepSec}/1e9 as load, cpu from sched ` +
-        `where ts >= ${startNs} and ts < ${endNs} and utid != 0 ` +
-        'group by cpu order by cpu');
+          `select cast(sum(dur) as float)/${
+              stepSize} as load, cpu from sched ` +
+          `where ts >= ${start} and ts < ${end} and utid != 0 ` +
+          'group by cpu order by cpu');
       const schedData: {[key: string]: QuantizedLoad} = {};
       const it = schedResult.iter({load: NUM, cpu: NUM});
       for (; it.valid(); it.next()) {
         const load = it.load;
         const cpu = it.cpu;
-        schedData[cpu] = {startSec, endSec, load};
+        schedData[cpu] = {start, end, load};
         hasSchedOverview = true;
       }
       publishOverviewData(schedData);
@@ -645,16 +662,15 @@
     }
 
     // Slices overview.
-    const traceStartNs = toNs(traceTime.start);
-    const stepSecNs = toNs(stepSec);
     const sliceResult = await engine.query(`select
            bucket,
            upid,
-           ifnull(sum(utid_sum) / cast(${stepSecNs} as float), 0) as load
+           ifnull(sum(utid_sum) / cast(${stepSize} as float), 0) as load
          from thread
          inner join (
            select
-             ifnull(cast((ts - ${traceStartNs})/${stepSecNs} as int), 0) as bucket,
+             ifnull(cast((ts - ${trace.start})/${
+        stepSize} as int), 0) as bucket,
              sum(dur) as utid_sum,
              utid
            from slice
@@ -665,21 +681,21 @@
          group by bucket, upid`);
 
     const slicesData: {[key: string]: QuantizedLoad[]} = {};
-    const it = sliceResult.iter({bucket: NUM, upid: NUM, load: NUM});
+    const it = sliceResult.iter({bucket: LONG, upid: NUM, load: NUM});
     for (; it.valid(); it.next()) {
       const bucket = it.bucket;
       const upid = it.upid;
       const load = it.load;
 
-      const startSec = traceTime.start + stepSec * bucket;
-      const endSec = startSec + stepSec;
+      const start = trace.start + stepSize * bucket;
+      const end = start + stepSize;
 
       const upidStr = upid.toString();
       let loadArray = slicesData[upidStr];
       if (loadArray === undefined) {
         loadArray = slicesData[upidStr] = [];
       }
-      loadArray.push({startSec, endSec, load});
+      loadArray.push({start, end, load});
     }
     publishOverviewData(slicesData);
   }
@@ -890,48 +906,48 @@
   }
 }
 
-async function computeTraceReliableRangeStart(engine: Engine): Promise<number> {
+async function computeTraceReliableRangeStart(engine: Engine): Promise<TPTime> {
   const result =
     await engine.query(`SELECT RUN_METRIC('chrome/chrome_reliable_range.sql');
        SELECT start FROM chrome_reliable_range`);
-  const bounds = result.firstRow({start: NUM});
-  return bounds.start / 1e9;
+  const bounds = result.firstRow({start: LONG});
+  return bounds.start;
 }
 
 async function computeVisibleTime(
-  traceStartSec: number,
-  traceEndSec: number,
-  isJsonTrace: boolean,
-  engine: Engine): Promise<[number, number]> {
+    traceStart: TPTime, traceEnd: TPTime, isJsonTrace: boolean, engine: Engine):
+    Promise<Span<HighPrecisionTime>> {
   // if we have non-default visible state, update the visible time to it
-  const previousVisibleState = globals.state.frontendLocalState.visibleState;
-  if (!(previousVisibleState.startSec === defaultTraceTime.startSec &&
-    previousVisibleState.endSec === defaultTraceTime.endSec) &&
-    (previousVisibleState.startSec >= traceStartSec &&
-      previousVisibleState.endSec <= traceEndSec)) {
-    return [previousVisibleState.startSec, previousVisibleState.endSec];
+  const previousVisibleState = globals.stateVisibleTime();
+  const defaultTraceSpan =
+      new TPTimeSpan(defaultTraceTime.start, defaultTraceTime.end);
+  if (!(previousVisibleState.start === defaultTraceSpan.start &&
+        previousVisibleState.end === defaultTraceSpan.end) &&
+      (previousVisibleState.start >= traceStart &&
+       previousVisibleState.end <= traceEnd)) {
+    return HighPrecisionTimeSpan.fromTpTime(
+        previousVisibleState.start, previousVisibleState.end);
   }
 
   // initialise visible time to the trace time bounds
-  let visibleStartSec = traceStartSec;
-  let visibleEndSec = traceEndSec;
+  let visibleStartSec = traceStart;
+  let visibleEndSec = traceEnd;
 
   // compare start and end with metadata computed by the trace processor
   const mdTime = await engine.getTracingMetadataTimeBounds();
   // make sure the bounds hold
-  if (Math.max(visibleStartSec, mdTime.start) <
-    Math.min(visibleEndSec, mdTime.end)) {
-    visibleStartSec =
-      Math.max(visibleStartSec, mdTime.start);
-    visibleEndSec = Math.min(visibleEndSec, mdTime.end);
+  if (BigintMath.max(visibleStartSec, mdTime.start) <
+      BigintMath.min(visibleEndSec, mdTime.end)) {
+    visibleStartSec = BigintMath.max(visibleStartSec, mdTime.start);
+    visibleEndSec = BigintMath.min(visibleEndSec, mdTime.end);
   }
 
   // Trace Processor doesn't support the reliable range feature for JSON
   // traces.
   if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ZOOM_FLAG.get()) {
     const reliableRangeStart = await computeTraceReliableRangeStart(engine);
-    visibleStartSec = Math.max(visibleStartSec, reliableRangeStart);
+    visibleStartSec = BigintMath.max(visibleStartSec, reliableRangeStart);
   }
 
-  return [visibleStartSec, visibleEndSec];
+  return HighPrecisionTimeSpan.fromTpTime(visibleStartSec, visibleEndSec);
 }
diff --git a/ui/src/controller/track_controller.ts b/ui/src/controller/track_controller.ts
index 46d1f1f..73b9896 100644
--- a/ui/src/controller/track_controller.ts
+++ b/ui/src/controller/track_controller.ts
@@ -12,11 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../base/bigint_math';
 import {assertExists, assertTrue} from '../base/logging';
 import {Engine} from '../common/engine';
 import {Registry} from '../common/registry';
 import {TraceTime, TrackState} from '../common/state';
-import {fromNs, toNs} from '../common/time';
+import {
+  TPDuration,
+  TPTime,
+  tpTimeFromSeconds,
+  TPTimeSpan,
+} from '../common/time';
 import {LIMIT, TrackData} from '../common/track_data';
 import {globals} from '../frontend/globals';
 import {publishTrackData} from '../frontend/publish';
@@ -70,7 +76,7 @@
   // Must be overridden by the track implementation. Is invoked when the track
   // frontend runs out of cached data. The derived track controller is expected
   // to publish new track data in response to this call.
-  abstract onBoundsChange(start: number, end: number, resolution: number):
+  abstract onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data>;
 
   get trackState(): TrackState {
@@ -129,6 +135,7 @@
   }
 
   shouldRequestData(traceTime: TraceTime): boolean {
+    const tspan = new TPTimeSpan(traceTime.start, traceTime.end);
     if (this.data === undefined) return true;
     if (this.shouldReload()) return true;
 
@@ -137,15 +144,14 @@
     if (atLimit) {
       // We request more data than the window, so add window duration to find
       // the previous window.
-      const prevWindowStart =
-          this.data.start + (traceTime.startSec - traceTime.endSec);
-      return traceTime.startSec !== prevWindowStart;
+      const prevWindowStart = this.data.start + tspan.duration;
+      return tspan.start !== prevWindowStart;
     }
 
     // Otherwise request more data only when out of range of current data or
     // resolution has changed.
-    const inRange = traceTime.startSec >= this.data.start &&
-        traceTime.endSec <= this.data.end;
+    const inRange =
+        tspan.start >= this.data.start && tspan.end <= this.data.end;
     return !inRange ||
         this.data.resolution !==
         globals.state.frontendLocalState.visibleState.resolution;
@@ -162,8 +168,7 @@
       return undefined;
     }
 
-    const bounds = globals.state.traceTime;
-    const traceDurNs = toNs(bounds.endSec - bounds.startSec);
+    const traceDuration = globals.stateTraceTime().duration;
 
     // For large traces, going through the raw table in the most zoomed-out
     // states can be very expensive as this can involve going through O(millions
@@ -198,7 +203,7 @@
     // Compute the outermost bucket size. This acts as a starting point for
     // computing the cached size.
     const outermostResolutionLevel =
-        Math.ceil(Math.log2(traceDurNs / approxWidthPx));
+        Math.ceil(Math.log2(traceDuration.nanos / approxWidthPx));
     const outermostBucketNs = Math.pow(2, outermostResolutionLevel);
 
     // This constant decides how many resolution levels down from our outermost
@@ -232,11 +237,11 @@
 
   run() {
     const visibleState = globals.state.frontendLocalState.visibleState;
-    if (visibleState === undefined || visibleState.resolution === undefined ||
-        visibleState.resolution === Infinity) {
+    if (visibleState === undefined) {
       return;
     }
-    const dur = visibleState.endSec - visibleState.startSec;
+    const visibleTimeSpan = globals.stateVisibleTime();
+    const dur = visibleTimeSpan.duration;
     if (globals.state.visibleTracks.includes(this.trackId) &&
         this.shouldRequestData(visibleState)) {
       if (this.requestingData) {
@@ -253,16 +258,14 @@
             .then(() => {
               this.isSetup = true;
               let resolution = visibleState.resolution;
-              // TODO(hjd): We shouldn't have to be so defensive here.
-              if (Math.log2(toNs(resolution)) % 1 !== 0) {
-                // resolution is in pixels per second so 1000 means
-                // 1px = 1ms.
-                resolution =
-                    fromNs(Math.pow(2, Math.floor(Math.log2(toNs(1000)))));
+
+              if (BigintMath.popcount(resolution) !== 1) {
+                resolution = BigintMath.bitFloor(tpTimeFromSeconds(1000));
               }
+
               return this.onBoundsChange(
-                  visibleState.startSec - dur,
-                  visibleState.endSec + dur,
+                  visibleTimeSpan.start - dur,
+                  visibleTimeSpan.end + dur,
                   resolution);
             })
             .then((data) => {
diff --git a/ui/src/controller/track_decider.ts b/ui/src/controller/track_decider.ts
index 241e248..4882400 100644
--- a/ui/src/controller/track_decider.ts
+++ b/ui/src/controller/track_decider.ts
@@ -25,6 +25,7 @@
 import {featureFlags, PERF_SAMPLE_FLAG} from '../common/feature_flags';
 import {pluginManager} from '../common/plugins';
 import {
+  LONG_NULL,
   NUM,
   NUM_NULL,
   STR,
@@ -61,6 +62,12 @@
   PROCESS_SCHEDULING_TRACK_KIND,
 } from '../tracks/process_scheduling';
 import {PROCESS_SUMMARY_TRACK} from '../tracks/process_summary';
+import {
+  ENABLE_SCROLL_JANK_PLUGIN_V2,
+  INPUT_LATENCY_TRACK,
+} from '../tracks/scroll_jank';
+import {addLatenciesTrack} from '../tracks/scroll_jank/event_latency_track';
+import {addTopLevelScrollTrack} from '../tracks/scroll_jank/scroll_track';
 import {THREAD_STATE_TRACK_KIND} from '../tracks/thread_state';
 
 const TRACKS_V2_FLAG = featureFlags.register({
@@ -207,6 +214,25 @@
     }
   }
 
+  async addScrollJankTracks(engine: Engine): Promise<void> {
+    const topLevelScrolls = addTopLevelScrollTrack(engine);
+    const topLevelScrollsResult = await topLevelScrolls;
+    let originalLength = this.tracksToAdd.length;
+    this.tracksToAdd.length += topLevelScrollsResult.tracksToAdd.length;
+    for (let i = 0; i < topLevelScrollsResult.tracksToAdd.length; ++i) {
+      this.tracksToAdd[i + originalLength] =
+          topLevelScrollsResult.tracksToAdd[i];
+    }
+
+    originalLength = this.tracksToAdd.length;
+    const eventLatencies = addLatenciesTrack(engine);
+    const eventLatencyResult = await eventLatencies;
+    this.tracksToAdd.length += eventLatencyResult.tracksToAdd.length;
+    for (let i = 0; i < eventLatencyResult.tracksToAdd.length; ++i) {
+      this.tracksToAdd[i + originalLength] = eventLatencyResult.tracksToAdd[i];
+    }
+  }
+
   async addCpuFreqTracks(engine: EngineProxy): Promise<void> {
     const cpus = await this.engine.getCpus();
 
@@ -265,20 +291,21 @@
 
   async addGlobalAsyncTracks(engine: EngineProxy): Promise<void> {
     const rawGlobalAsyncTracks = await engine.query(`
-      with global_tracks as materialized (
+      with tracks_with_slices as materialized (
+        select distinct track_id
+        from slice
+      ),
+      global_tracks as (
         select
           track.parent_id as parent_id,
           track.id as track_id,
-          track.name as name,
-          count(1) cnt
+          track.name as name
         from track
-        join slice on slice.track_id = track.id
+        join tracks_with_slices on tracks_with_slices.track_id = track.id
         where
           track.type = "track"
           or track.type = "gpu_track"
           or track.type = "cpu_track"
-        group by track_id
-        having cnt > 0
       ),
       global_tracks_grouped as (
         select
@@ -308,6 +335,7 @@
     });
 
     const parentIdToGroupId = new Map<number, string>();
+    let scrollJankRendered = false;
 
     for (; it.valid(); it.next()) {
       const kind = ASYNC_SLICE_TRACK_KIND;
@@ -352,6 +380,13 @@
         }
       }
 
+      if (ENABLE_SCROLL_JANK_PLUGIN_V2.get() && !scrollJankRendered &&
+          name.includes(INPUT_LATENCY_TRACK)) {
+        // This ensures that the scroll jank tracks render above the tracks
+        // for GestureScrollUpdate.
+        await this.addScrollJankTracks(this.engine);
+        scrollJankRendered = true;
+      }
       const track = {
         engineId: this.engineId,
         kind,
@@ -405,45 +440,6 @@
     }
   }
 
-  async addGlobalCounterTracks(engine: EngineProxy): Promise<void> {
-    // Add global or GPU counter tracks that are not bound to any pid/tid.
-    const globalCounters = await engine.query(`
-    select name, id
-    from (
-      select name, id
-      from counter_track
-      where type = 'counter_track'
-      union
-      select name, id
-      from gpu_counter_track
-      where name != 'gpufreq'
-    )
-    order by name
-  `);
-
-    const it = globalCounters.iter({
-      name: STR,
-      id: NUM,
-    });
-
-    for (; it.valid(); it.next()) {
-      const name = it.name;
-      const trackId = it.id;
-      this.tracksToAdd.push({
-        engineId: this.engineId,
-        kind: COUNTER_TRACK_KIND,
-        name,
-        trackSortKey: PrimaryTrackSortKey.COUNTER_TRACK,
-        trackGroup: SCROLLING_TRACK_GROUP,
-        config: {
-          name,
-          trackId,
-          scale: getCounterScale(name),
-        },
-      });
-    }
-  }
-
   async addCpuPerfCounterTracks(engine: EngineProxy): Promise<void> {
     // Perf counter tracks are bound to CPUs, follow the scheduling and
     // frequency track naming convention ("Cpu N ...").
@@ -720,15 +716,12 @@
   }
 
   async addFtraceTrack(engine: EngineProxy): Promise<void> {
-    const query = `select
-            cpu,
-            count(*) as cnt
+    const query = `select distinct cpu
           from ftrace_event
-          where cpu + 1 > 1 or utid + 1 > 1
-          group by cpu`;
+          where cpu + 1 > 1 or utid + 1 > 1`;
 
     const result = await engine.query(query);
-    const it = result.iter({cpu: NUM, cnt: NUM});
+    const it = result.iter({cpu: NUM});
 
     let groupUuid = undefined;
     let summaryTrackId = undefined;
@@ -1000,9 +993,9 @@
       upid: NUM_NULL,
       tid: NUM_NULL,
       threadName: STR_NULL,
-      startTs: NUM_NULL,
+      startTs: LONG_NULL,
       trackId: NUM,
-      endTs: NUM_NULL,
+      endTs: LONG_NULL,
     });
     for (; it.valid(); it.next()) {
       const utid = it.utid;
@@ -1317,8 +1310,8 @@
       upid: NUM,
       pid: NUM_NULL,
       processName: STR_NULL,
-      startTs: NUM_NULL,
-      endTs: NUM_NULL,
+      startTs: LONG_NULL,
+      endTs: LONG_NULL,
     });
     for (let i = 0; it.valid(); ++i, it.next()) {
       const pid = it.pid;
diff --git a/ui/src/frontend/aggregation_panel.ts b/ui/src/frontend/aggregation_panel.ts
index ddcb6ba..6bd6422 100644
--- a/ui/src/frontend/aggregation_panel.ts
+++ b/ui/src/frontend/aggregation_panel.ts
@@ -22,6 +22,7 @@
 } from '../common/aggregation_data';
 import {colorForState, textColorForState} from '../common/colorizer';
 import {translateState} from '../common/thread_state';
+import {tpTimeToMillis} from '../common/time';
 
 import {globals} from './globals';
 import {Panel} from './panel';
@@ -111,7 +112,8 @@
     const selection = globals.state.currentSelection;
     if (selection === null || selection.kind !== 'AREA') return undefined;
     const selectedArea = globals.state.areas[selection.areaId];
-    const rangeDurationMs = (selectedArea.endSec - selectedArea.startSec) * 1e3;
+    const rangeDurationMs =
+        tpTimeToMillis(selectedArea.end - selectedArea.start);
     return m('.time-range', `Selected range: ${rangeDurationMs.toFixed(6)} ms`);
   }
 
diff --git a/ui/src/frontend/base_slice_track.ts b/ui/src/frontend/base_slice_track.ts
index ac3cad0..77c6e6d 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -22,7 +22,12 @@
 } from '../common/colorizer';
 import {NUM} from '../common/query_result';
 import {Selection, SelectionKind} from '../common/state';
-import {fromNs, toNs} from '../common/time';
+import {
+  fromNs,
+  tpDurationFromNanos,
+  TPTime,
+  tpTimeFromNanos,
+} from '../common/time';
 
 import {checkerboardExcept} from './checkerboard';
 import {globals} from './globals';
@@ -45,7 +50,7 @@
 // Exposed and standalone to allow for testing without making this
 // visible to subclasses.
 function filterVisibleSlices<S extends Slice>(
-    slices: S[], startS: number, endS: number): S[] {
+    slices: S[], start: TPTime, end: TPTime): S[] {
   // Here we aim to reduce the number of slices we have to draw
   // by ignoring those that are not visible. A slice is visible iff:
   //   slice.start + slice.duration >= start && slice.start <= end
@@ -89,7 +94,7 @@
   // For all slice in slices: slice.startS > endS (e.g. all slices are to the
   // right). Since the slices are sorted by startS we can check this easily:
   const maybeFirstSlice: S|undefined = slices[0];
-  if (maybeFirstSlice && maybeFirstSlice.startS > endS) {
+  if (maybeFirstSlice && maybeFirstSlice.start > end) {
     return [];
   }
   // It's not possible to easily check the analogous edge case where all slices
@@ -108,15 +113,15 @@
   let endIdx = slices.length;
   for (; startIdx < endIdx; ++startIdx) {
     const slice = slices[startIdx];
-    const sliceEndS = slice.startS + slice.durationS;
-    if (sliceEndS >= startS && slice.startS <= endS) {
+    const sliceEndS = slice.start + slice.duration;
+    if (sliceEndS >= start && slice.start <= end) {
       break;
     }
   }
   for (; startIdx < endIdx; --endIdx) {
     const slice = slices[endIdx - 1];
-    const sliceEndS = slice.startS + slice.durationS;
-    if (sliceEndS >= startS && slice.startS <= endS) {
+    const sliceEndS = slice.start + slice.duration;
+    if (sliceEndS >= start && slice.start <= end) {
       break;
     }
   }
@@ -181,7 +186,7 @@
   private cache: TrackCache<Array<CastInternal<T['slice']>>> =
       new TrackCache(5);
 
-  private readonly tableName: string;
+  protected readonly tableName: string;
   private maxDurNs = 0;
   private sqlState: 'UNINITIALIZED'|'INITIALIZING'|'QUERY_PENDING'|
       'QUERY_DONE' = 'UNINITIALIZED';
@@ -272,13 +277,16 @@
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     // TODO(hjd): fonts and colors should come from the CSS and not hardcoded
     // here.
-    const timeScale = globals.frontendLocalState.timeScale;
-    const vizTime = globals.frontendLocalState.visibleWindowTime;
+    const {
+      visibleTimeScale: timeScale,
+      visibleWindowTime: vizTime,
+    } = globals.frontendLocalState;
 
     {
-      const windowSizePx = Math.max(1, timeScale.endPx - timeScale.startPx);
-      const rawStartNs = toNs(vizTime.start);
-      const rawEndNs = toNs(vizTime.end);
+      const windowSizePx = Math.max(1, timeScale.pxSpan.delta);
+      // TODO(stevegolton): Keep these guys as bigints
+      const rawStartNs = vizTime.start.nanos;
+      const rawEndNs = vizTime.end.nanos;
       const rawSlicesKey = CacheKey.create(rawStartNs, rawEndNs, windowSizePx);
 
       // If the visible time range is outside the cached area, requests
@@ -298,7 +306,8 @@
     // Filter only the visible slices. |this.slices| will have more slices than
     // needed because maybeRequestData() over-fetches to handle small pan/zooms.
     // We don't want to waste time drawing slices that are off screen.
-    const vizSlices = this.getVisibleSlicesInternal(vizTime.start, vizTime.end);
+    const vizSlices = this.getVisibleSlicesInternal(
+        vizTime.start.toTPTime('floor'), vizTime.end.toTPTime('ceil'));
 
     let selection = globals.state.currentSelection;
 
@@ -321,15 +330,15 @@
     // pxEnd is the last visible pixel in the visible viewport. Drawing
     // anything < 0 or > pxEnd doesn't produce any visible effect as it goes
     // beyond the visible portion of the canvas.
-    const pxEnd = Math.floor(timeScale.timeToPx(vizTime.end));
+    const pxEnd = Math.floor(timeScale.hpTimeToPx(vizTime.end));
 
     for (const slice of vizSlices) {
       // Compute the basic geometry for any visible slice, even if only
       // partially visible. This might end up with a negative x if the
       // slice starts before the visible time or with a width that overflows
       // pxEnd.
-      slice.x = timeScale.timeToPx(slice.startS);
-      slice.w = timeScale.deltaTimeToPx(slice.durationS);
+      slice.x = timeScale.tpTimeToPx(slice.start);
+      slice.w = timeScale.durationToPx(slice.duration);
       if (slice.flags & SLICE_FLAGS_INSTANT) {
         // In the case of an instant slice, set the slice geometry on the
         // bounding box that will contain the chevron.
@@ -429,10 +438,10 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        timeScale.timeToPx(vizTime.start),
-        timeScale.timeToPx(vizTime.end),
-        timeScale.timeToPx(fromNs(this.slicesKey.startNs)),
-        timeScale.timeToPx(fromNs(this.slicesKey.endNs)));
+        timeScale.hpTimeToPx(vizTime.start),
+        timeScale.hpTimeToPx(vizTime.end),
+        timeScale.secondsToPx(fromNs(this.slicesKey.startNs)),
+        timeScale.secondsToPx(fromNs(this.slicesKey.endNs)));
 
     // TODO(hjd): Remove this.
     // The only thing this does is drawing the sched latency arrow. We should
@@ -623,8 +632,8 @@
 
     return {
       id: row.id,
-      startS: fromNs(startNsQ),
-      durationS: fromNs(endNsQ - startNsQ),
+      start: tpTimeFromNanos(startNsQ),
+      duration: tpDurationFromNanos(endNsQ - startNsQ),
       flags,
       depth: row.depth,
       title: '',
@@ -701,10 +710,10 @@
     return true;
   }
 
-  private getVisibleSlicesInternal(startS: number, endS: number):
+  private getVisibleSlicesInternal(start: TPTime, end: TPTime):
       Array<CastInternal<T['slice']>> {
     return filterVisibleSlices<CastInternal<T['slice']>>(
-        this.slices, startS, endS);
+        this.slices, start, end);
   }
 
   private updateSliceAndTrackHeight() {
diff --git a/ui/src/frontend/base_slice_track_unittest.ts b/ui/src/frontend/base_slice_track_unittest.ts
index 7dd109d..e9202a2 100644
--- a/ui/src/frontend/base_slice_track_unittest.ts
+++ b/ui/src/frontend/base_slice_track_unittest.ts
@@ -19,11 +19,11 @@
 } from './base_slice_track';
 import {Slice} from './slice';
 
-function slice(startS: number, durationS: number): Slice {
+function slice(start: number, duration: number): Slice {
   return {
     id: 42,
-    startS,
-    durationS,
+    start: BigInt(start),
+    duration: BigInt(duration),
     depth: 0,
     flags: 0,
     title: '',
@@ -36,24 +36,24 @@
 const s = slice;
 
 test('filterVisibleSlices', () => {
-  expect(filterVisibleSlices([], 0, 100)).toEqual([]);
-  expect(filterVisibleSlices([s(10, 80)], 0, 100)).toEqual([s(10, 80)]);
-  expect(filterVisibleSlices([s(0, 20)], 10, 100)).toEqual([s(0, 20)]);
-  expect(filterVisibleSlices([s(0, 10)], 10, 100)).toEqual([s(0, 10)]);
-  expect(filterVisibleSlices([s(100, 10)], 10, 100)).toEqual([s(100, 10)]);
-  expect(filterVisibleSlices([s(10, 0)], 10, 100)).toEqual([s(10, 0)]);
-  expect(filterVisibleSlices([s(100, 0)], 10, 100)).toEqual([s(100, 0)]);
-  expect(filterVisibleSlices([s(0, 5)], 10, 90)).toEqual([]);
-  expect(filterVisibleSlices([s(95, 5)], 10, 90)).toEqual([]);
-  expect(filterVisibleSlices([s(0, 5), s(95, 5)], 10, 90)).toEqual([]);
+  expect(filterVisibleSlices([], 0n, 100n)).toEqual([]);
+  expect(filterVisibleSlices([s(10, 80)], 0n, 100n)).toEqual([s(10, 80)]);
+  expect(filterVisibleSlices([s(0, 20)], 10n, 100n)).toEqual([s(0, 20)]);
+  expect(filterVisibleSlices([s(0, 10)], 10n, 100n)).toEqual([s(0, 10)]);
+  expect(filterVisibleSlices([s(100, 10)], 10n, 100n)).toEqual([s(100, 10)]);
+  expect(filterVisibleSlices([s(10, 0)], 10n, 100n)).toEqual([s(10, 0)]);
+  expect(filterVisibleSlices([s(100, 0)], 10n, 100n)).toEqual([s(100, 0)]);
+  expect(filterVisibleSlices([s(0, 5)], 10n, 90n)).toEqual([]);
+  expect(filterVisibleSlices([s(95, 5)], 10n, 90n)).toEqual([]);
+  expect(filterVisibleSlices([s(0, 5), s(95, 5)], 10n, 90n)).toEqual([]);
   expect(filterVisibleSlices(
              [
                s(0, 5),
                s(50, 0),
                s(95, 5),
              ],
-             10,
-             90))
+             10n,
+             90n))
       .toEqual([
         s(50, 0),
       ]);
@@ -63,8 +63,8 @@
                s(1, 9),
                s(6, 3),
              ],
-             10,
-             90))
+             10n,
+             90n))
       .toContainEqual(s(1, 9));
   expect(filterVisibleSlices(
              [
@@ -73,16 +73,16 @@
                s(6, 3),
                s(50, 0),
              ],
-             10,
-             90))
+             10n,
+             90n))
       .toContainEqual(s(1, 9));
   expect(filterVisibleSlices(
              [
                s(85, 10),
                s(100, 10),
              ],
-             10,
-             90))
+             10n,
+             90n))
       .toEqual([
         s(85, 10),
       ]);
@@ -91,8 +91,8 @@
                s(0, 100),
 
              ],
-             10,
-             90))
+             10n,
+             90n))
       .toEqual([
         s(0, 100),
       ]);
@@ -109,7 +109,7 @@
                s(8, 1),
                s(9, 1),
              ],
-             10,
-             90))
+             10n,
+             90n))
       .toContainEqual(s(5, 10));
 });
diff --git a/ui/src/frontend/chrome_slice_panel.ts b/ui/src/frontend/chrome_slice_panel.ts
index 6c982cf..50e3d1c 100644
--- a/ui/src/frontend/chrome_slice_panel.ts
+++ b/ui/src/frontend/chrome_slice_panel.ts
@@ -17,7 +17,9 @@
 import {sqliteString} from '../base/string_utils';
 import {Actions} from '../common/actions';
 import {Arg, ArgsTree, isArgTreeArray, isArgTreeMap} from '../common/arg_types';
-import {timeToCode} from '../common/time';
+import {EngineProxy} from '../common/engine';
+import {runQuery} from '../common/queries';
+import {timeToCode, tpDurationToSeconds, tpTimeToCode} from '../common/time';
 
 import {FlowPoint, globals, SliceDetails} from './globals';
 import {PanelSize} from './panel';
@@ -54,6 +56,29 @@
         ),
   },
   {
+    name: 'Binder call names',
+    shouldDisplay: () => true,
+    getAction: (slice: SliceDetails) => {
+      const engine = getEngine();
+      if (engine === undefined) return;
+      runQuery(`SELECT IMPORT('android.binder');`, engine)
+          .then(
+              () => runQueryInNewTab(
+                  `
+                SELECT s.ts, s.dur, tx.aidl_name AS name, s.id
+                FROM android_sync_binder_metrics_by_txn tx
+                  JOIN slice s ON tx.binder_txn_id = s.id
+                  JOIN thread_track ON s.track_id = thread_track.id
+                  JOIN thread USING (utid)
+                  JOIN process USING (upid)
+                WHERE aidl_name IS NOT NULL
+                  AND pid = ${slice.pid}
+                  AND tid = ${slice.tid}`,
+                  `Binder names (${slice.processName}:${slice.tid})`,
+                  ));
+    },
+  },
+  {
     name: 'Lock graph',
     shouldDisplay: (slice: SliceDetails) => slice.id !== undefined,
     getAction: (slice: SliceDetails) => runQueryInNewTab(
@@ -110,6 +135,15 @@
   });
 }
 
+function getEngine(): EngineProxy|undefined {
+  const engineId = globals.getCurrentEngine()?.id;
+  if (engineId === undefined) {
+    return undefined;
+  }
+  const engine = globals.engines.get(engineId)?.getProxy('SlicePanel');
+  return engine;
+}
+
 // Table row contents is one of two things:
 // 1. Key-value pair
 interface TableRow {
@@ -261,7 +295,9 @@
           !sliceInfo.category || sliceInfo.category === '[NULL]' ?
               'N/A' :
               sliceInfo.category);
-      defaultBuilder.add('Start time', timeToCode(sliceInfo.ts));
+      defaultBuilder.add(
+          'Start time',
+          tpTimeToCode(sliceInfo.ts - globals.state.traceTime.start));
       if (sliceInfo.absTime !== undefined) {
         defaultBuilder.add('Absolute Time', sliceInfo.absTime);
       }
@@ -271,9 +307,11 @@
           sliceInfo.threadDur !== undefined) {
         // If we have valid thread duration, also display a percentage of
         // |threadDur| compared to |dur|.
-        const threadDurFractionSuffix = sliceInfo.threadDur === -1 ?
+        const ratio = tpDurationToSeconds(sliceInfo.threadDur) /
+            tpDurationToSeconds(sliceInfo.dur);
+        const threadDurFractionSuffix = sliceInfo.threadDur === -1n ?
             '' :
-            ` (${(sliceInfo.threadDur / sliceInfo.dur * 100).toFixed(2)}%)`;
+            ` (${(ratio * 100).toFixed(2)}%)`;
         defaultBuilder.add(
             'Thread duration',
             this.computeDuration(sliceInfo.threadTs, sliceInfo.threadDur) +
diff --git a/ui/src/frontend/counter_panel.ts b/ui/src/frontend/counter_panel.ts
index 99d3841..4237773 100644
--- a/ui/src/frontend/counter_panel.ts
+++ b/ui/src/frontend/counter_panel.ts
@@ -14,8 +14,7 @@
 
 import m from 'mithril';
 
-import {fromNs, timeToCode} from '../common/time';
-
+import {tpTimeToCode} from '../common/time';
 import {globals} from './globals';
 import {Panel} from './panel';
 
@@ -37,7 +36,11 @@
                    m('tr', m('th', `Name`), m('td', `${counterInfo.name}`)),
                    m('tr',
                      m('th', `Start time`),
-                     m('td', `${timeToCode(counterInfo.startTime)}`)),
+                     m('td',
+                       `${
+                           tpTimeToCode(
+                               counterInfo.startTime -
+                               globals.state.traceTime.start)}`)),
                    m('tr',
                      m('th', `Value`),
                      m('td', `${counterInfo.value.toLocaleString()}`)),
@@ -46,7 +49,7 @@
                      m('td', `${counterInfo.delta.toLocaleString()}`)),
                    m('tr',
                      m('th', `Duration`),
-                     m('td', `${timeToCode(fromNs(counterInfo.duration))}`)),
+                     m('td', `${tpTimeToCode(counterInfo.duration)}`)),
                  ])],
               ));
     } else {
diff --git a/ui/src/frontend/debug.ts b/ui/src/frontend/debug.ts
index fae7a83..7e9c9e5 100644
--- a/ui/src/frontend/debug.ts
+++ b/ui/src/frontend/debug.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {produce} from 'immer';
 import m from 'mithril';
 
 import {Actions} from '../common/actions';
@@ -19,12 +20,14 @@
 
 import {globals} from './globals';
 
+
 declare global {
   interface Window {
     m: typeof m;
     getSchema: typeof getSchema;
     globals: typeof globals;
     Actions: typeof Actions;
+    produce: typeof produce;
   }
 }
 
@@ -33,4 +36,5 @@
   window.m = m;
   window.globals = globals;
   window.Actions = Actions;
+  window.produce = produce;
 }
diff --git a/ui/src/frontend/details_panel.ts b/ui/src/frontend/details_panel.ts
index 6e5a985..e3a7585 100644
--- a/ui/src/frontend/details_panel.ts
+++ b/ui/src/frontend/details_panel.ts
@@ -17,9 +17,12 @@
 import {Actions} from '../common/actions';
 import {isEmptyData} from '../common/aggregation_data';
 import {LogExists, LogExistsKey} from '../common/logs';
+import {pluginManager} from '../common/plugins';
 import {addSelectionChangeObserver} from '../common/selection_observer';
 import {Selection} from '../common/state';
 import {DebugSliceDetailsTab} from '../tracks/debug/details_tab';
+import {SCROLL_JANK_PLUGIN_ID} from '../tracks/scroll_jank';
+import {TOP_LEVEL_SCROLL_KIND} from '../tracks/scroll_jank/scroll_track';
 
 import {AggregationPanel} from './aggregation_panel';
 import {ChromeSliceDetailsPanel} from './chrome_slice_panel';
@@ -45,6 +48,8 @@
 const DOWN_ICON = 'keyboard_arrow_down';
 const DRAG_HANDLE_HEIGHT_PX = 28;
 
+export const CURRENT_SELECTION_TAG = 'current_selection';
+
 function getDetailsHeight() {
   // This needs to be a function instead of a const to ensure the CSS constants
   // have been initialized by the time we perform this calculation;
@@ -178,7 +183,7 @@
 }
 
 function handleSelectionChange(newSelection?: Selection, _?: Selection): void {
-  const currentSelectionTag = 'current_selection';
+  const currentSelectionTag = CURRENT_SELECTION_TAG;
   const bottomTabList = globals.bottomTabList;
   if (!bottomTabList) return;
   if (newSelection === undefined) {
@@ -225,6 +230,10 @@
         },
       });
       break;
+    case TOP_LEVEL_SCROLL_KIND:
+      pluginManager.onDetailsPanelSelectionChange(
+          SCROLL_JANK_PLUGIN_ID, newSelection);
+      break;
     default:
       bottomTabList.closeTabByTag(currentSelectionTag);
   }
diff --git a/ui/src/frontend/drag/border_drag_strategy.ts b/ui/src/frontend/drag/border_drag_strategy.ts
index df450fc..564ffc3 100644
--- a/ui/src/frontend/drag/border_drag_strategy.ts
+++ b/ui/src/frontend/drag/border_drag_strategy.ts
@@ -12,28 +12,27 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 import {TimeScale} from '../time_scale';
-
 import {DragStrategy} from './drag_strategy';
 
 export class BorderDragStrategy extends DragStrategy {
   private moveStart = false;
 
-  constructor(timeScale: TimeScale, private pixelBounds: [number, number]) {
-    super(timeScale);
+  constructor(map: TimeScale, private pixelBounds: [number, number]) {
+    super(map);
   }
 
   onDrag(x: number) {
-    let tStart =
-        this.timeScale.pxToTime(this.moveStart ? x : this.pixelBounds[0]);
-    let tEnd =
-        this.timeScale.pxToTime(!this.moveStart ? x : this.pixelBounds[1]);
-    if (tStart > tEnd) {
+    let tStart = this.map.pxToHpTime(this.moveStart ? x : this.pixelBounds[0]);
+    let tEnd = this.map.pxToHpTime(!this.moveStart ? x : this.pixelBounds[1]);
+    if (tStart.isGreaterThan(tEnd)) {
       this.moveStart = !this.moveStart;
       [tEnd, tStart] = [tStart, tEnd];
     }
     super.updateGlobals(tStart, tEnd);
-    this.pixelBounds =
-        [this.timeScale.timeToPx(tStart), this.timeScale.timeToPx(tEnd)];
+    this.pixelBounds = [
+      this.map.hpTimeToPx(tStart),
+      this.map.hpTimeToPx(tEnd),
+    ];
   }
 
   onDragStart(x: number) {
diff --git a/ui/src/frontend/drag/drag_strategy.ts b/ui/src/frontend/drag/drag_strategy.ts
index 2896849..afb83e1 100644
--- a/ui/src/frontend/drag/drag_strategy.ts
+++ b/ui/src/frontend/drag/drag_strategy.ts
@@ -11,19 +11,22 @@
 // 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 {TimeSpan} from '../../common/time';
+import {
+  HighPrecisionTime,
+  HighPrecisionTimeSpan,
+} from '../../common/high_precision_time';
 import {globals} from '../globals';
 import {TimeScale} from '../time_scale';
 
 export abstract class DragStrategy {
-  constructor(protected timeScale: TimeScale) {}
+  constructor(protected map: TimeScale) {}
 
   abstract onDrag(x: number): void;
 
   abstract onDragStart(x: number): void;
 
-  protected updateGlobals(tStart: number, tEnd: number) {
-    const vizTime = new TimeSpan(tStart, tEnd);
+  protected updateGlobals(tStart: HighPrecisionTime, tEnd: HighPrecisionTime) {
+    const vizTime = new HighPrecisionTimeSpan(tStart, tEnd);
     globals.frontendLocalState.updateVisibleTime(vizTime);
     globals.rafScheduler.scheduleRedraw();
   }
diff --git a/ui/src/frontend/drag/inner_drag_strategy.ts b/ui/src/frontend/drag/inner_drag_strategy.ts
index 2af1b39..7be7f7b 100644
--- a/ui/src/frontend/drag/inner_drag_strategy.ts
+++ b/ui/src/frontend/drag/inner_drag_strategy.ts
@@ -23,8 +23,8 @@
 
   onDrag(x: number) {
     const move = x - this.dragStartPx;
-    const tStart = this.timeScale.pxToTime(this.pixelBounds[0] + move);
-    const tEnd = this.timeScale.pxToTime(this.pixelBounds[1] + move);
+    const tStart = this.map.pxToHpTime(this.pixelBounds[0] + move);
+    const tEnd = this.map.pxToHpTime(this.pixelBounds[1] + move);
     super.updateGlobals(tStart, tEnd);
   }
 
diff --git a/ui/src/frontend/drag/outer_drag_strategy.ts b/ui/src/frontend/drag/outer_drag_strategy.ts
index 648b50d..f8269fc 100644
--- a/ui/src/frontend/drag/outer_drag_strategy.ts
+++ b/ui/src/frontend/drag/outer_drag_strategy.ts
@@ -11,16 +11,19 @@
 // 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 {
+  HighPrecisionTime,
+} from '../../common/high_precision_time';
 import {DragStrategy} from './drag_strategy';
 
 export class OuterDragStrategy extends DragStrategy {
   private dragStartPx = 0;
 
   onDrag(x: number) {
-    const dragBeginTime = this.timeScale.pxToTime(this.dragStartPx);
-    const dragEndTime = this.timeScale.pxToTime(x);
-    const tStart = Math.min(dragBeginTime, dragEndTime);
-    const tEnd = Math.max(dragBeginTime, dragEndTime);
+    const dragBeginTime = this.map.pxToHpTime(this.dragStartPx);
+    const dragEndTime = this.map.pxToHpTime(x);
+    const tStart = HighPrecisionTime.min(dragBeginTime, dragEndTime);
+    const tEnd = HighPrecisionTime.max(dragBeginTime, dragEndTime);
     super.updateGlobals(tStart, tEnd);
   }
 
diff --git a/ui/src/frontend/drag_gesture_handler.ts b/ui/src/frontend/drag_gesture_handler.ts
index 30c0a38..4c547aa 100644
--- a/ui/src/frontend/drag_gesture_handler.ts
+++ b/ui/src/frontend/drag_gesture_handler.ts
@@ -33,8 +33,6 @@
     document.body.addEventListener('mousemove', this.boundOnMouseMove);
     document.body.addEventListener('mouseup', this.boundOnMouseUp);
     this.pendingMouseDownEvent = e;
-    // Prevent interactions with other DragGestureHandlers and event listeners
-    e.stopPropagation();
   }
 
   // We don't start the drag gesture on mouse down, instead we wait until
@@ -60,17 +58,15 @@
       this.onDrag(
           e.clientX - this.clientRect!.left, e.clientY - this.clientRect!.top);
     }
-    e.stopPropagation();
   }
 
-  private onMouseUp(e: MouseEvent) {
+  private onMouseUp(_e: MouseEvent) {
     this._isDragging = false;
     document.body.removeEventListener('mousemove', this.boundOnMouseMove);
     document.body.removeEventListener('mouseup', this.boundOnMouseUp);
     if (!this.pendingMouseDownEvent) {
       this.onDragFinished();
     }
-    e.stopPropagation();
   }
 
   get isDragging() {
diff --git a/ui/src/frontend/flamegraph_panel.ts b/ui/src/frontend/flamegraph_panel.ts
index b8246b3..0ac5f79 100644
--- a/ui/src/frontend/flamegraph_panel.ts
+++ b/ui/src/frontend/flamegraph_panel.ts
@@ -28,10 +28,9 @@
   FlamegraphStateViewingOption,
   ProfileType,
 } from '../common/state';
-import {timeToCode} from '../common/time';
+import {tpTimeToCode} from '../common/time';
 import {profileType} from '../controller/flamegraph_controller';
 
-import {PerfettoMouseEvent} from './events';
 import {Flamegraph, NodeRendering} from './flamegraph';
 import {globals} from './globals';
 import {Modal, ModalDefinition} from './modal';
@@ -41,6 +40,7 @@
 import {getCurrentTrace} from './sidebar';
 import {convertTraceToPprofAndDownload} from './trace_converter';
 import {Button} from './widgets/button';
+import {findRef} from './widgets/utils';
 
 interface FlamegraphDetailsPanelAttrs {}
 
@@ -64,23 +64,24 @@
 
 export class FlamegraphDetailsPanel extends Panel<FlamegraphDetailsPanelAttrs> {
   private profileType?: ProfileType = undefined;
-  private ts = 0;
+  private ts = 0n;
   private pids: number[] = [];
   private flamegraph: Flamegraph = new Flamegraph([]);
   private focusRegex = '';
   private updateFocusRegexDebounced = debounce(() => {
     this.updateFocusRegex();
   }, 20);
+  private canvas?: HTMLCanvasElement;
 
   view() {
     const flamegraphDetails = globals.flamegraphDetails;
     if (flamegraphDetails && flamegraphDetails.type !== undefined &&
-        flamegraphDetails.startNs !== undefined &&
-        flamegraphDetails.durNs !== undefined &&
+        flamegraphDetails.start !== undefined &&
+        flamegraphDetails.dur !== undefined &&
         flamegraphDetails.pids !== undefined &&
         flamegraphDetails.upids !== undefined) {
       this.profileType = profileType(flamegraphDetails.type);
-      this.ts = flamegraphDetails.startNs + flamegraphDetails.durNs;
+      this.ts = flamegraphDetails.start + flamegraphDetails.dur;
       this.pids = flamegraphDetails.pids;
       if (flamegraphDetails.flamegraph) {
         this.flamegraph.updateDataIfChanged(
@@ -91,25 +92,6 @@
           0;
       return m(
           '.details-panel',
-          {
-            onclick: (e: PerfettoMouseEvent) => {
-              if (this.flamegraph !== undefined) {
-                this.onMouseClick({y: e.layerY, x: e.layerX});
-              }
-              return false;
-            },
-            onmousemove: (e: PerfettoMouseEvent) => {
-              if (this.flamegraph !== undefined) {
-                this.onMouseMove({y: e.layerY, x: e.layerX});
-                globals.rafScheduler.scheduleRedraw();
-              }
-            },
-            onmouseout: () => {
-              if (this.flamegraph !== undefined) {
-                this.onMouseOut();
-              }
-            },
-          },
           this.maybeShowModal(flamegraphDetails.graphIncomplete),
           m('.details-panel-heading.flamegraph-profile',
             {onclick: (e: MouseEvent) => e.stopPropagation()},
@@ -126,7 +108,7 @@
                         toSelectedCallsite(
                             flamegraphDetails.expandedCallsite)}`),
                   m('div.time',
-                    `Snapshot time: ${timeToCode(flamegraphDetails.durNs)}`),
+                    `Snapshot time: ${tpTimeToCode(flamegraphDetails.dur)}`),
                   m('input[type=text][placeholder=Focus]', {
                     oninput: (e: Event) => {
                       const target = (e.target as HTMLInputElement);
@@ -146,7 +128,20 @@
                       }),
                 ]),
             ]),
-          m(`div[style=height:${height}px]`),
+          m(`canvas[ref=canvas]`, {
+            style: `height:${height}px; width:100%`,
+            onmousemove: (e: MouseEvent) => {
+              const {offsetX, offsetY} = e;
+              this.onMouseMove({x: offsetX, y: offsetY});
+            },
+            onmouseout: () => {
+              this.onMouseOut();
+            },
+            onclick: (e: MouseEvent) => {
+              const {offsetX, offsetY} = e;
+              this.onMouseClick({x: offsetX, y: offsetY});
+            },
+          }),
       );
     } else {
       return m(
@@ -256,7 +251,53 @@
         this.nodeRendering(), flamegraphData, data.expandedCallsite);
   }
 
-  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
+  oncreate({dom}: m.CVnodeDOM<FlamegraphDetailsPanelAttrs>) {
+    this.canvas = FlamegraphDetailsPanel.findCanvasElement(dom);
+    // TODO(stevegolton): If we truely want to be standalone, then we shouldn't
+    // rely on someone else calling the rafScheduler when the window is resized,
+    // but it's good enough for now as we know the ViewerPage will do it.
+    globals.rafScheduler.addRedrawCallback(this.rafRedrawCallback);
+  }
+
+  onupdate({dom}: m.CVnodeDOM<FlamegraphDetailsPanelAttrs>) {
+    this.canvas = FlamegraphDetailsPanel.findCanvasElement(dom);
+  }
+
+  onremove(_vnode: m.CVnodeDOM<FlamegraphDetailsPanelAttrs>) {
+    globals.rafScheduler.removeRedrawCallback(this.rafRedrawCallback);
+  }
+
+  private static findCanvasElement(dom: Element): HTMLCanvasElement|undefined {
+    const canvas = findRef(dom, 'canvas');
+    if (canvas && canvas instanceof HTMLCanvasElement) {
+      return canvas;
+    } else {
+      return undefined;
+    }
+  }
+
+  private rafRedrawCallback = () => {
+    if (this.canvas) {
+      const canvas = this.canvas;
+      canvas.width = canvas.offsetWidth * devicePixelRatio;
+      canvas.height = canvas.offsetHeight * devicePixelRatio;
+      const ctx = canvas.getContext('2d');
+      if (ctx) {
+        ctx.clearRect(0, 0, canvas.width, canvas.height);
+        ctx.save();
+        ctx.scale(devicePixelRatio, devicePixelRatio);
+        const {offsetWidth: width, offsetHeight: height} = canvas;
+        this.renderLocalCanvas(ctx, {width, height});
+        ctx.restore();
+      }
+    }
+  };
+
+  renderCanvas() {
+    // No-op
+  }
+
+  private renderLocalCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
     this.changeFlamegraphData();
     const current = globals.state.currentFlamegraphState;
     if (current === null) return;
@@ -265,22 +306,24 @@
             current.viewingOption === ALLOC_SPACE_MEMORY_ALLOCATED_KEY ?
         'B' :
         '';
-    this.flamegraph.draw(ctx, size.width, size.height, 0, HEADER_HEIGHT, unit);
+    this.flamegraph.draw(ctx, size.width, size.height, 0, 0, unit);
   }
 
-  onMouseClick({x, y}: {x: number, y: number}): boolean {
+  private onMouseClick({x, y}: {x: number, y: number}): boolean {
     const expandedCallsite = this.flamegraph.onMouseClick({x, y});
     globals.dispatch(Actions.expandFlamegraphState({expandedCallsite}));
     return true;
   }
 
-  onMouseMove({x, y}: {x: number, y: number}): boolean {
+  private onMouseMove({x, y}: {x: number, y: number}): boolean {
     this.flamegraph.onMouseMove({x, y});
+    globals.rafScheduler.scheduleFullRedraw();
     return true;
   }
 
-  onMouseOut() {
+  private onMouseOut() {
     this.flamegraph.onMouseOut();
+    globals.rafScheduler.scheduleFullRedraw();
   }
 
   private static selectViewingOptions(profileType: ProfileType) {
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index 909dcb7..fdb91e0 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -140,7 +140,7 @@
   }
 
   private getXCoordinate(ts: number): number {
-    return globals.frontendLocalState.timeScale.timeToPx(ts);
+    return globals.frontendLocalState.visibleTimeScale.secondsToPx(ts);
   }
 
   private getSliceRect(args: FlowEventsRendererArgs, point: FlowPoint):
diff --git a/ui/src/frontend/frontend_local_state.ts b/ui/src/frontend/frontend_local_state.ts
index b24f69a..f4d95ca 100644
--- a/ui/src/frontend/frontend_local_state.ts
+++ b/ui/src/frontend/frontend_local_state.ts
@@ -14,6 +14,10 @@
 
 import {assertTrue} from '../base/logging';
 import {Actions} from '../common/actions';
+import {
+  HighPrecisionTime,
+  HighPrecisionTimeSpan,
+} from '../common/high_precision_time';
 import {HttpRpcState} from '../common/http_rpc_engine';
 import {
   Area,
@@ -21,11 +25,15 @@
   Timestamped,
   VisibleState,
 } from '../common/state';
-import {TimeSpan} from '../common/time';
+import {Span} from '../common/time';
+import {
+  TPTime,
+  TPTimeSpan,
+} from '../common/time';
 
 import {globals} from './globals';
 import {ratelimit} from './rate_limiters';
-import {TimeScale} from './time_scale';
+import {PxSpan, TimeScale} from './time_scale';
 
 interface Range {
   start?: number;
@@ -42,10 +50,6 @@
   return current;
 }
 
-function capBetween(t: number, start: number, end: number) {
-  return Math.min(Math.max(t, start), end);
-}
-
 // Calculate the space a scrollbar takes up so that we can subtract it from
 // the canvas width.
 function calculateScrollbarWidth() {
@@ -60,13 +64,99 @@
   return width;
 }
 
+export class TimeWindow {
+  private readonly MIN_DURATION_NS = 10;
+  private _start: HighPrecisionTime = new HighPrecisionTime();
+  private _durationNanos: number = 10e9;
+
+  private get _end(): HighPrecisionTime {
+    return this._start.addNanos(this._durationNanos);
+  }
+
+  update(span: Span<HighPrecisionTime>) {
+    this._start = span.start;
+    this._durationNanos = Math.max(this.MIN_DURATION_NS, span.duration.nanos);
+    this.preventClip();
+  }
+
+  // Pan the window by certain number of seconds
+  pan(offset: HighPrecisionTime) {
+    this._start = this._start.add(offset);
+    this.preventClip();
+  }
+
+  // Zoom in or out a bit centered on a specific offset from the root
+  // Offset represents the center of the zoom as a normalized value between 0
+  // and 1 where 0 is the start of the time window and 1 is the end
+  zoom(ratio: number, offset: number) {
+    // TODO(stevegolton): Handle case where trace time < MIN_DURATION_NS
+
+    const traceDuration = globals.stateTraceTime().duration;
+    const minDuration = Math.min(this.MIN_DURATION_NS, traceDuration.nanos);
+    const newDurationNanos = Math.max(this._durationNanos * ratio, minDuration);
+    // Delta between new and old duration
+    // +ve if new duration is shorter than old duration
+    const durationDeltaNanos = this._durationNanos - newDurationNanos;
+    // If offset is 0, don't move the start at all
+    // If offset if 1, move the start by the amount the duration has changed
+    // If new duration is shorter - move start to right
+    // If new duration is longer - move start to left
+    this._start = this._start.addNanos(durationDeltaNanos * offset);
+    this._durationNanos = newDurationNanos;
+    this.preventClip();
+  }
+
+  createTimeScale(startPx: number, endPx: number): TimeScale {
+    return new TimeScale(
+        this._start, this._durationNanos, new PxSpan(startPx, endPx));
+  }
+
+  // Get timespan covering entire range of the window
+  get timeSpan(): HighPrecisionTimeSpan {
+    return new HighPrecisionTimeSpan(this._start, this._end);
+  }
+
+  get timestampSpan(): Span<TPTime> {
+    return new TPTimeSpan(this.earliest, this.latest);
+  }
+
+  get earliest(): TPTime {
+    return this._start.toTPTime('floor');
+  }
+
+  get latest(): TPTime {
+    return this._start.addNanos(this._durationNanos).toTPTime('ceil');
+  }
+
+  // Limit the zoom and pan
+  private preventClip() {
+    const traceTimeSpan = globals.stateTraceTime();
+    const traceDurationNanos = traceTimeSpan.duration.nanos;
+
+    if (this._durationNanos > traceDurationNanos) {
+      this._start = traceTimeSpan.start;
+      this._durationNanos = traceDurationNanos;
+    }
+
+    if (this._start.isLessThan(traceTimeSpan.start)) {
+      this._start = traceTimeSpan.start;
+    }
+
+    const end = this._start.addNanos(this._durationNanos);
+    if (end.isGreaterThan(traceTimeSpan.end)) {
+      this._start = traceTimeSpan.end.subtractNanos(this._durationNanos);
+    }
+  }
+}
+
 /**
  * State that is shared between several frontend components, but not the
  * controller. This state is updated at 60fps.
  */
 export class FrontendLocalState {
-  visibleWindowTime = new TimeSpan(0, 10);
-  timeScale = new TimeScale(this.visibleWindowTime, [0, 0]);
+  visibleWindow = new TimeWindow();
+  startPx: number = 0;
+  endPx: number = 0;
   showPanningHint = false;
   showCookieConsent = false;
   visibleTracks = new Set<string>();
@@ -82,9 +172,9 @@
 
   private _visibleState: VisibleState = {
     lastUpdate: 0,
-    startSec: 0,
-    endSec: 10,
-    resolution: 1,
+    start: 0n,
+    end: BigInt(10e9),
+    resolution: 1n,
   };
 
   private _selectedArea?: Area;
@@ -125,6 +215,16 @@
     }
   }
 
+  zoomVisibleWindow(ratio: number, centerPoint: number) {
+    this.visibleWindow.zoom(ratio, centerPoint);
+    this.kickUpdateLocalState();
+  }
+
+  panVisibleWindow(delta: HighPrecisionTime) {
+    this.visibleWindow.pan(delta);
+    this.kickUpdateLocalState();
+  }
+
   mergeState(state: FrontendState): void {
     // This is unfortunately subtle. This class mutates this._visibleState.
     // Since we may not mutate |state| (in order to make immer's immutable
@@ -137,17 +237,22 @@
     this._visibleState = chooseLatest(this._visibleState, state.visibleState);
     const visibleStateWasUpdated = previousVisibleState !== this._visibleState;
     if (visibleStateWasUpdated) {
-      this.updateLocalTime(
-          new TimeSpan(this._visibleState.startSec, this._visibleState.endSec));
+      this.updateLocalTime(new HighPrecisionTimeSpan(
+          HighPrecisionTime.fromTPTime(this._visibleState.start),
+          HighPrecisionTime.fromTPTime(this._visibleState.end),
+          ));
     }
   }
 
+  // Set the highlight box to draw
   selectArea(
-      startSec: number, endSec: number,
+      start: TPTime, end: TPTime,
       tracks = this._selectedArea ? this._selectedArea.tracks : []) {
-    assertTrue(endSec >= startSec);
+    assertTrue(
+        end >= start,
+        `Impossible select area: start [${start}] >= end [${end}]`);
     this.showPanningHint = true;
-    this._selectedArea = {startSec, endSec, tracks},
+    this._selectedArea = {start, end, tracks},
     globals.rafScheduler.scheduleFullRedraw();
   }
 
@@ -164,12 +269,11 @@
     globals.dispatch(Actions.setVisibleTraceTime(this._visibleState));
   }, 50);
 
-  private updateLocalTime(ts: TimeSpan) {
-    const traceTime = globals.state.traceTime;
-    const startSec = capBetween(ts.start, traceTime.startSec, traceTime.endSec);
-    const endSec = capBetween(ts.end, traceTime.startSec, traceTime.endSec);
-    this.visibleWindowTime = new TimeSpan(startSec, endSec);
-    this.timeScale.setTimeBounds(this.visibleWindowTime);
+  private updateLocalTime(ts: Span<HighPrecisionTime>) {
+    const traceBounds = globals.stateTraceTime();
+    const start = ts.start.clamp(traceBounds.start, traceBounds.end);
+    const end = ts.end.clamp(traceBounds.start, traceBounds.end);
+    this.visibleWindow.update(new HighPrecisionTimeSpan(start, end));
     this.updateResolution();
   }
 
@@ -179,17 +283,17 @@
     this.ratelimitedUpdateVisible();
   }
 
-  updateVisibleTime(ts: TimeSpan) {
-    this.updateLocalTime(ts);
+  private kickUpdateLocalState() {
     this._visibleState.lastUpdate = Date.now() / 1000;
-    this._visibleState.startSec = this.visibleWindowTime.start;
-    this._visibleState.endSec = this.visibleWindowTime.end;
+    this._visibleState.start = this.visibleWindowTime.start.toTPTime();
+    this._visibleState.end = this.visibleWindowTime.end.toTPTime();
     this._visibleState.resolution = globals.getCurResolution();
     this.ratelimitedUpdateVisible();
   }
 
-  getVisibleStateBounds(): [number, number] {
-    return [this.visibleWindowTime.start, this.visibleWindowTime.end];
+  updateVisibleTime(ts: Span<HighPrecisionTime>) {
+    this.updateLocalTime(ts);
+    this.kickUpdateLocalState();
   }
 
   // Whenever start/end px of the timeScale is changed, update
@@ -200,7 +304,28 @@
     pxStart = Math.max(0, pxStart);
     pxEnd = Math.max(0, pxEnd);
     if (pxStart === pxEnd) pxEnd = pxStart + 1;
-    this.timeScale.setLimitsPx(pxStart, pxEnd);
+    this.startPx = pxStart;
+    this.endPx = pxEnd;
     this.updateResolution();
   }
+
+  // Get the time scale for the visible window
+  get visibleTimeScale(): TimeScale {
+    return this.visibleWindow.createTimeScale(this.startPx, this.endPx);
+  }
+
+  // Produces a TimeScale object for this time window provided start and end px
+  getTimeScale(startPx: number, endPx: number): TimeScale {
+    return this.visibleWindow.createTimeScale(startPx, endPx);
+  }
+
+  // Get the bounds of the window in pixels
+  get windowSpan(): PxSpan {
+    return new PxSpan(this.startPx, this.endPx);
+  }
+
+  // Get the bounds of the visible time window as a time span
+  get visibleWindowTime(): Span<HighPrecisionTime> {
+    return this.visibleWindow.timeSpan;
+  }
 }
diff --git a/ui/src/frontend/ftrace_panel.ts b/ui/src/frontend/ftrace_panel.ts
index 29d0207..e45b5fe 100644
--- a/ui/src/frontend/ftrace_panel.ts
+++ b/ui/src/frontend/ftrace_panel.ts
@@ -18,7 +18,7 @@
 import {assertExists} from '../base/logging';
 import {Actions} from '../common/actions';
 import {colorForString} from '../common/colorizer';
-import {formatTimestamp} from '../common/time';
+import {formatTPTime, TPTime} from '../common/time';
 
 import {globals} from './globals';
 import {Panel} from './panel';
@@ -105,6 +105,11 @@
   onremove({dom}: m.CVnodeDOM) {
     const sc = this.scrollContainer(dom);
     sc.removeEventListener('scroll', this.onScroll);
+
+    globals.dispatch(Actions.updateFtracePagination({
+      offset: 0,
+      count: 0,
+    }));
   }
 
   onScroll = (e: Event) => {
@@ -112,12 +117,12 @@
     this.recomputeVisibleRowsAndUpdate(scrollContainer);
   };
 
-  onRowOver(ts: number) {
+  onRowOver(ts: TPTime) {
     globals.dispatch(Actions.setHoverCursorTimestamp({ts}));
   }
 
   onRowOut() {
-    globals.dispatch(Actions.setHoverCursorTimestamp({ts: -1}));
+    globals.dispatch(Actions.setHoverCursorTimestamp({ts: -1n}));
   }
 
   private renderRowsLabel() {
@@ -183,8 +188,7 @@
       for (let i = 0; i < events.length; i++) {
         const {ts, name, cpu, process, args} = events[i];
 
-        const timestamp =
-            formatTimestamp(ts / 1e9 - globals.state.traceTime.startSec);
+        const timestamp = formatTPTime(ts - globals.state.traceTime.start);
 
         const rank = i + offset;
 
@@ -199,7 +203,7 @@
             `.row`,
             {
               style: {top: `${(rank + 1.0) * ROW_H}px`},
-              onmouseover: this.onRowOver.bind(this, ts / 1e9),
+              onmouseover: this.onRowOver.bind(this, ts),
               onmouseout: this.onRowOut.bind(this),
             },
             m('.cell', timestamp),
diff --git a/ui/src/frontend/globals.ts b/ui/src/frontend/globals.ts
index 0ca10a0..79ca58a 100644
--- a/ui/src/frontend/globals.ts
+++ b/ui/src/frontend/globals.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../base/bigint_math';
 import {assertExists} from '../base/logging';
 import {Actions, DeferredAction} from '../common/actions';
 import {AggregateData} from '../common/aggregation_data';
@@ -22,10 +23,19 @@
 } from '../common/conversion_jobs';
 import {createEmptyState} from '../common/empty_state';
 import {Engine} from '../common/engine';
+import {
+  HighPrecisionTime,
+  HighPrecisionTimeSpan,
+} from '../common/high_precision_time';
 import {MetricResult} from '../common/metric_data';
 import {CurrentSearchResults, SearchSummary} from '../common/search_data';
 import {CallsiteInfo, EngineConfig, ProfileType, State} from '../common/state';
-import {fromNs, toNs} from '../common/time';
+import {Span, tpTimeFromSeconds} from '../common/time';
+import {
+  TPDuration,
+  TPTime,
+  TPTimeSpan,
+} from '../common/time';
 
 import {Analytics, initAnalytics} from './analytics';
 import {BottomTabList} from './bottom_tab';
@@ -33,6 +43,7 @@
 import {RafScheduler} from './raf_scheduler';
 import {Router} from './router';
 import {ServiceWorkerController} from './service_worker_controller';
+import {PxSpan, TimeScale} from './time_scale';
 
 type Dispatch = (action: DeferredAction) => void;
 type TrackDataStore = Map<string, {}>;
@@ -41,18 +52,18 @@
 type Description = Map<string, string>;
 
 export interface SliceDetails {
-  ts?: number;
+  ts?: TPTime;
   absTime?: string;
-  dur?: number;
-  threadTs?: number;
-  threadDur?: number;
+  dur?: TPDuration;
+  threadTs?: TPTime;
+  threadDur?: TPDuration;
   priority?: number;
   endState?: string|null;
   cpu?: number;
   id?: number;
   threadStateId?: number;
   utid?: number;
-  wakeupTs?: number;
+  wakeupTs?: TPTime;
   wakerUtid?: number;
   wakerCpu?: number;
   category?: string;
@@ -104,23 +115,23 @@
 }
 
 export interface CounterDetails {
-  startTime?: number;
+  startTime?: TPTime;
   value?: number;
   delta?: number;
-  duration?: number;
+  duration?: TPDuration;
   name?: string;
 }
 
 export interface ThreadStateDetails {
-  ts?: number;
-  dur?: number;
+  ts?: TPTime;
+  dur?: TPDuration;
 }
 
 export interface FlamegraphDetails {
   type?: ProfileType;
   id?: number;
-  startNs?: number;
-  durNs?: number;
+  start?: TPTime;
+  dur?: TPDuration;
   pids?: number[];
   upids?: number[];
   flamegraph?: CallsiteInfo[];
@@ -143,8 +154,8 @@
 }
 
 export interface QuantizedLoad {
-  startSec: number;
-  endSec: number;
+  start: TPTime;
+  end: TPTime;
   load: number;
 }
 type OverviewStore = Map<string, QuantizedLoad[]>;
@@ -161,7 +172,7 @@
 
 export interface FtraceEvent {
   id: number;
-  ts: number;
+  ts: TPTime;
   name: string;
   cpu: number;
   thread: string|null;
@@ -530,7 +541,7 @@
     this.aggregateDataStore.set(kind, data);
   }
 
-  getCurResolution() {
+  getCurResolution(): TPDuration {
     // Truncate the resolution to the closest power of 2 (in nanosecond space).
     // We choose to work in ns space because resolution is consumed be track
     // controllers for quantization and they rely on resolution to be a power
@@ -541,24 +552,18 @@
     // levels. Logic: each zoom level represents a delta of 0.1 * (visible
     // window span). Therefore, zooming out by six levels is 1.1^6 ~= 2.
     // Similarily, zooming in six levels is 0.9^6 ~= 0.5.
-    const pxToSec = this.frontendLocalState.timeScale.deltaPxToDuration(1);
+    const timeScale = this.frontendLocalState.visibleTimeScale;
     // TODO(b/186265930): Remove once fixed:
-    if (!isFinite(pxToSec)) {
-      // Resolution is in pixels per second so 1000 means 1px = 1ms.
-      console.error(`b/186265930: Bad pxToSec suppressed ${pxToSec}`);
-      return fromNs(Math.pow(2, Math.floor(Math.log2(toNs(1000)))));
+    if (timeScale.pxSpan.delta === 0) {
+      console.error(`b/186265930: Bad pxToSec suppressed`);
+      return BigintMath.bitFloor(tpTimeFromSeconds(1000));
     }
-    const pxToNs = Math.max(toNs(pxToSec), 1);
-    const resolution = fromNs(Math.pow(2, Math.floor(Math.log2(pxToNs))));
-    const log2 = Math.log2(toNs(resolution));
-    if (log2 % 1 !== 0) {
-      throw new Error(`Resolution should be a power of two.
-        pxToSec: ${pxToSec},
-        pxToNs: ${pxToNs},
-        resolution: ${resolution},
-        log2: ${Math.log2(toNs(resolution))}`);
-    }
-    return resolution;
+
+    const timePerPx = HighPrecisionTime.max(
+        timeScale.pxDeltaToDuration(1), new HighPrecisionTime(1n));
+
+    const resolutionBig = BigintMath.bitFloor(timePerPx.toTPTime());
+    return resolutionBig;
   }
 
   getCurrentEngine(): EngineConfig|undefined {
@@ -637,6 +642,30 @@
   shutdown() {
     this._rafScheduler!.shutdown();
   }
+
+  // Get a timescale that covers the entire trace
+  getTraceTimeScale(pxSpan: PxSpan): TimeScale {
+    const {start, end} = this.state.traceTime;
+    const traceTime = HighPrecisionTimeSpan.fromTpTime(start, end);
+    return new TimeScale(traceTime.start, traceTime.duration.nanos, pxSpan);
+  }
+
+  // Get the trace time bounds
+  stateTraceTime(): Span<HighPrecisionTime> {
+    const {start, end} = this.state.traceTime;
+    return HighPrecisionTimeSpan.fromTpTime(start, end);
+  }
+
+  stateTraceTimeTP(): Span<TPTime> {
+    const {start, end} = this.state.traceTime;
+    return new TPTimeSpan(start, end);
+  }
+
+  // Get the state version of the visible time bounds
+  stateVisibleTime(): Span<TPTime> {
+    const {start, end} = this.state.frontendLocalState.visibleState;
+    return new TPTimeSpan(start, end);
+  }
 }
 
 export const globals = new Globals();
diff --git a/ui/src/frontend/gridline_helper.ts b/ui/src/frontend/gridline_helper.ts
index 1c9dbfe..6581c81 100644
--- a/ui/src/frontend/gridline_helper.ts
+++ b/ui/src/frontend/gridline_helper.ts
@@ -13,49 +13,91 @@
 // limitations under the License.
 
 import {assertTrue} from '../base/logging';
-import {roundDownNearest} from '../base/math_utils';
+import {Span, tpDurationToSeconds} from '../common/time';
+import {TPDuration, TPTime, TPTimeSpan} from '../common/time';
+
 import {TRACK_BORDER_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
 import {globals} from './globals';
 import {TimeScale} from './time_scale';
 
-// Returns the optimal step size (in seconds) and tick pattern of ticks within
-// the step. The returned step size has two properties: (1) It is 1, 2, or 5,
-// multiplied by some integer power of 10. (2) It is maximised given the
-// constraint: |range| / stepSize <= |maxNumberOfSteps|.
-export function getStepSize(
-    range: number, maxNumberOfSteps: number): [number, string] {
-  // First, get the largest possible power of 10 that is smaller than the
-  // desired step size, and use it as our initial step size.
-  // For example, if the range is 2345ms and the desired steps is 10, then the
-  // minimum step size is 234.5ms so the step size will initialise to 100.
-  const minStepSize = range / maxNumberOfSteps;
-  const zeros = Math.floor(Math.log10(minStepSize));
-  const initialStepSize = Math.pow(10, zeros);
+const micros = 1000n;
+const millis = 1000n * micros;
+const seconds = 1000n * millis;
+const minutes = 60n * seconds;
+const hours = 60n * minutes;
+const days = 24n * hours;
 
-  // We know that |initialStepSize| is a power of 10, and
-  // initialStepSize <= desiredStepSize <= 10 * initialStepSize. There are four
-  // possible candidates for final step size: 1, 2, 5 or 10 * initialStepSize.
-  // For our example above, this would result in a step size of 500ms, as both
-  // 100ms and 200ms are smaller than the minimum step size of 234.5ms.
-  // We pick the candidate that minimizes the step size without letting the
-  // number of steps exceed |maxNumberOfSteps|. The factor we pick to also
-  // determines the pattern of ticks. This pattern is represented using a string
-  // where:
-  //  | = Major tick
-  //  : = Medium tick
-  //  . = Minor tick
-  const stepSizeMultipliers: [number, string][] =
-      [[1, '|....:....'], [2, '|.:.'], [5, '|....'], [10, '|....:....']];
+// These patterns cover the entire range of 0 - 2^63-1 nanoseconds
+const patterns: [bigint, string][] = [
+  [1n, '|'],
+  [2n, '|:'],
+  [5n, '|....'],
+  [10n, '|....:....'],
+  [20n, '|.:.'],
+  [50n, '|....'],
+  [100n, '|....:....'],
+  [200n, '|.:.'],
+  [500n, '|....'],
+  [1n * micros, '|....:....'],
+  [2n * micros, '|.:.'],
+  [5n * micros, '|....'],
+  [10n * micros, '|....:....'],
+  [20n * micros, '|.:.'],
+  [50n * micros, '|....'],
+  [100n * micros, '|....:....'],
+  [200n * micros, '|.:.'],
+  [500n * micros, '|....'],
+  [1n * millis, '|....:....'],
+  [2n * millis, '|.:.'],
+  [5n * millis, '|....'],
+  [10n * millis, '|....:....'],
+  [20n * millis, '|.:.'],
+  [50n * millis, '|....'],
+  [100n * millis, '|....:....'],
+  [200n * millis, '|.:.'],
+  [500n * millis, '|....'],
+  [1n * seconds, '|....:....'],
+  [2n * seconds, '|.:.'],
+  [5n * seconds, '|....'],
+  [10n * seconds, '|....:....'],
+  [30n * seconds, '|.:.:.'],
+  [1n * minutes, '|.....'],
+  [2n * minutes, '|.:.'],
+  [5n * minutes, '|.....'],
+  [10n * minutes, '|....:....'],
+  [30n * minutes, '|.:.:.'],
+  [1n * hours, '|.....'],
+  [2n * hours, '|.:.'],
+  [6n * hours, '|.....'],
+  [12n * hours, '|.....:.....'],
+  [1n * days, '|.:.'],
+  [2n * days, '|.:.'],
+  [5n * days, '|....'],
+  [10n * days, '|....:....'],
+  [20n * days, '|.:.'],
+  [50n * days, '|....'],
+  [100n * days, '|....:....'],
+  [200n * days, '|.:.'],
+  [500n * days, '|....'],
+  [1000n * days, '|....:....'],
+  [2000n * days, '|.:.'],
+  [5000n * days, '|....'],
+  [10000n * days, '|....:....'],
+  [20000n * days, '|.:.'],
+  [50000n * days, '|....'],
+  [100000n * days, '|....:....'],
+  [200000n * days, '|.:.'],
+];
 
-  for (const [multiplier, pattern] of stepSizeMultipliers) {
-    const newStepSize = multiplier * initialStepSize;
-    const numberOfNewSteps = range / newStepSize;
-    if (numberOfNewSteps <= maxNumberOfSteps) {
-      return [newStepSize, pattern];
+// Returns the optimal step size and pattern of ticks within the step.
+export function getPattern(minPatternSize: bigint): [TPDuration, string] {
+  for (const [size, pattern] of patterns) {
+    if (size >= minPatternSize) {
+      return [size, pattern];
     }
   }
 
-  throw new Error('Something has gone horribly wrong with maths');
+  throw new Error('Pattern not defined for this minsize');
 }
 
 function tickPatternToArray(pattern: string): TickType[] {
@@ -75,21 +117,23 @@
   });
 }
 
-// Assuming a number only has one non-zero decimal digit, find the number of
-// decimal places required to accurately print that number. I.e. the parameter
-// we should pass to number.toFixed(x). To account for floating point
-// innaccuracies when representing numbers in base-10, we only take the first
-// nonzero fractional digit into account. E.g.
+// Get the number of decimal places we would have to print a time to for a given
+// min step size. For example, if we know the min step size is 0.1 and all
+// values are going to be aligned to integral multiples of 0.1, there's no
+// point printing these values with more than 1 decimal place.
+// Note: It's assumed that stepSize only has one significant figure.
+// E.g. 0.3 and 0.00002 are fine, but 0.123 will be treated as if it were 0.1.
+// Some examples: (seconds -> decimal places)
 //  1.0 -> 0
 //  0.5 -> 1
 //  0.009 -> 3
 //  0.00007 -> 5
 //  30000 -> 0
 //  0.30000000000000004 -> 1
-export function guessDecimalPlaces(val: number): number {
-  const neglog10 = -Math.floor(Math.log10(val));
-  const clamped = Math.max(0, neglog10);
-  return clamped;
+export function guessDecimalPlaces(stepSize: TPDuration): number {
+  const stepSizeSeconds = tpDurationToSeconds(stepSize);
+  const decimalPlaces = -Math.floor(Math.log10(stepSizeSeconds));
+  return Math.max(0, decimalPlaces);
 }
 
 export enum TickType {
@@ -100,55 +144,58 @@
 
 export interface Tick {
   type: TickType;
-  time: number;
-  position: number;
+  time: TPTime;
 }
 
 const MIN_PX_PER_STEP = 80;
+export function getMaxMajorTicks(width: number) {
+  return Math.max(1, Math.floor(width / MIN_PX_PER_STEP));
+}
+
+function roundDownNearest(time: TPTime, stepSize: TPDuration): TPTime {
+  return stepSize * (time / stepSize);
+}
 
 // An iterable which generates a series of ticks for a given timescale.
 export class TickGenerator implements Iterable<Tick> {
   private _tickPattern: TickType[];
-  private _patternSize: number;
+  private _patternSize: TPDuration;
+  private _timeSpan: Span<TPTime>;
+  private _offset: TPTime;
 
-  constructor(private scale: TimeScale, {minLabelPx = MIN_PX_PER_STEP} = {}) {
-    assertTrue(minLabelPx > 0, 'minLabelPx cannot be lte 0');
-    assertTrue(scale.widthPx > 0, 'widthPx cannot be lte 0');
-    assertTrue(
-        scale.timeSpan.duration > 0, 'timeSpan.duration cannot be lte 0');
+  constructor(
+      timeSpan: Span<TPTime>, maxMajorTicks: number, offset: TPTime = 0n) {
+    assertTrue(timeSpan.duration > 0n, 'timeSpan.duration cannot be lte 0');
+    assertTrue(maxMajorTicks > 0, 'maxMajorTicks cannot be lte 0');
 
-    const desiredSteps = scale.widthPx / minLabelPx;
-    const [size, pattern] = getStepSize(scale.timeSpan.duration, desiredSteps);
+    this._timeSpan = timeSpan.add(-offset);
+    this._offset = offset;
+    const minStepSize =
+        BigInt(Math.floor(Number(timeSpan.duration) / maxMajorTicks));
+    const [size, pattern] = getPattern(minStepSize);
     this._patternSize = size;
     this._tickPattern = tickPatternToArray(pattern);
   }
 
   // Returns an iterable, so this object can be iterated over directly using the
   // `for x of y` notation. The use of a generator here is just to make things
-  // more elegant than creating an array of ticks and building an iterator for
-  // it.
+  // more elegant compared to creating an array of ticks and building an
+  // iterator for it.
   * [Symbol.iterator](): Generator<Tick> {
-    const span = this.scale.timeSpan;
-    const stepSize = this._patternSize / this._tickPattern.length;
-    const start = roundDownNearest(span.start, this._patternSize);
-    const timeAtStep = (i: number) => start + (i * stepSize);
+    const stepSize = this._patternSize / BigInt(this._tickPattern.length);
+    const start = roundDownNearest(this._timeSpan.start, this._patternSize);
+    const end = this._timeSpan.end;
+    let patternIndex = 0;
 
-    // Iterating using steps instead of
-    // for (let s = start; s < span.end; s += stepSize) because if start is much
-    // larger than stepSize we can enter an infinite loop due to floating
-    // point precision errors.
-    for (let i = 0; timeAtStep(i) < span.end; i++) {
-      const time = timeAtStep(i);
-      if (time >= span.start) {
-        const position = Math.floor(this.scale.timeToPx(time));
-        const type = this._tickPattern[i % this._tickPattern.length];
-        yield {type, time, position};
+    for (let time = start; time < end; time += stepSize, patternIndex++) {
+      if (time >= this._timeSpan.start) {
+        patternIndex = patternIndex % this._tickPattern.length;
+        const type = this._tickPattern[patternIndex];
+        yield {type, time: time + this._offset};
       }
     }
   }
 
-  // The number of decimal places labels should be printed with, assuming labels
-  // are only printed on major ticks.
   get digits(): number {
     return guessDecimalPlaces(this._patternSize);
   }
@@ -157,9 +204,7 @@
 // Gets the timescale associated with the current visible window.
 export function timeScaleForVisibleWindow(
     startPx: number, endPx: number): TimeScale {
-  const span = globals.frontendLocalState.visibleWindowTime;
-  const spanRelative = span.add(-globals.state.traceTime.startSec);
-  return new TimeScale(spanRelative, [startPx, endPx]);
+  return globals.frontendLocalState.getTimeScale(startPx, endPx);
 }
 
 export function drawGridLines(
@@ -169,13 +214,18 @@
   ctx.strokeStyle = TRACK_BORDER_COLOR;
   ctx.lineWidth = 1;
 
-  const timeScale = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, width);
-  if (timeScale.timeSpan.duration > 0 && timeScale.widthPx > 0) {
-    for (const {type, position} of new TickGenerator(timeScale)) {
+  const {earliest, latest} = globals.frontendLocalState.visibleWindow;
+  const span = new TPTimeSpan(earliest, latest);
+  if (width > TRACK_SHELL_WIDTH && span.duration > 0n) {
+    const maxMajorTicks = getMaxMajorTicks(width - TRACK_SHELL_WIDTH);
+    const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, width);
+    for (const {type, time} of new TickGenerator(
+             span, maxMajorTicks, globals.state.traceTime.start)) {
+      const px = Math.floor(map.tpTimeToPx(time));
       if (type === TickType.MAJOR) {
         ctx.beginPath();
-        ctx.moveTo(position + 0.5, 0);
-        ctx.lineTo(position + 0.5, height);
+        ctx.moveTo(px + 0.5, 0);
+        ctx.lineTo(px + 0.5, height);
         ctx.stroke();
       }
     }
diff --git a/ui/src/frontend/gridline_helper_unittest.ts b/ui/src/frontend/gridline_helper_unittest.ts
index 3b6dcac..2454680 100644
--- a/ui/src/frontend/gridline_helper_unittest.ts
+++ b/ui/src/frontend/gridline_helper_unittest.ts
@@ -12,303 +12,93 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {TimeSpan} from '../common/time';
+import {TPTimeSpan} from '../common/time';
 
-import {getStepSize, Tick, TickGenerator, TickType} from './gridline_helper';
-import {TimeScale} from './time_scale';
-
-const pattern1 = '|....:....';
-const pattern2 = '|.:.';
-const pattern5 = '|....';
-const timeScale = new TimeScale(new TimeSpan(0, 1), [1, 2]);
+import {getPattern, TickGenerator, TickType} from './gridline_helper';
 
 test('gridline helper to have sensible step sizes', () => {
-  expect(getStepSize(10, 14)).toEqual([1, pattern1]);
-  expect(getStepSize(30, 14)).toEqual([5, pattern5]);
-  expect(getStepSize(60, 14)).toEqual([5, pattern5]);
-  expect(getStepSize(100, 14)).toEqual([10, pattern1]);
+  expect(getPattern(1n)).toEqual([1n, '|']);
+  expect(getPattern(2n)).toEqual([2n, '|:']);
+  expect(getPattern(3n)).toEqual([5n, '|....']);
+  expect(getPattern(4n)).toEqual([5n, '|....']);
+  expect(getPattern(5n)).toEqual([5n, '|....']);
+  expect(getPattern(7n)).toEqual([10n, '|....:....']);
 
-  expect(getStepSize(10, 21)).toEqual([0.5, pattern5]);
-  expect(getStepSize(30, 21)).toEqual([2, pattern2]);
-  expect(getStepSize(60, 21)).toEqual([5, pattern5]);
-  expect(getStepSize(100, 21)).toEqual([5, pattern5]);
+  expect(getPattern(10n)).toEqual([10n, '|....:....']);
+  expect(getPattern(20n)).toEqual([20n, '|.:.']);
+  expect(getPattern(50n)).toEqual([50n, '|....']);
 
-  expect(getStepSize(10, 3)).toEqual([5, pattern5]);
-  expect(getStepSize(30, 3)).toEqual([10, pattern1]);
-  expect(getStepSize(60, 3)).toEqual([20, pattern2]);
-  expect(getStepSize(100, 3)).toEqual([50, pattern5]);
-
-  expect(getStepSize(800, 4)).toEqual([200, pattern2]);
+  expect(getPattern(100n)).toEqual([100n, '|....:....']);
 });
 
-test('gridline helper to scale to very small and very large values', () => {
-  expect(getStepSize(.01, 14)).toEqual([.001, pattern1]);
-  expect(getStepSize(10000, 14)).toEqual([1000, pattern1]);
-});
+describe('TickGenerator', () => {
+  it('can generate ticks with span starting at origin', () => {
+    const tickGen = new TickGenerator(new TPTimeSpan(0n, 10n), 1);
+    const expected = [
+      {type: TickType.MAJOR, time: 0n},
+      {type: TickType.MINOR, time: 1n},
+      {type: TickType.MINOR, time: 2n},
+      {type: TickType.MINOR, time: 3n},
+      {type: TickType.MINOR, time: 4n},
+      {type: TickType.MEDIUM, time: 5n},
+      {type: TickType.MINOR, time: 6n},
+      {type: TickType.MINOR, time: 7n},
+      {type: TickType.MINOR, time: 8n},
+      {type: TickType.MINOR, time: 9n},
+    ];
+    const actual = Array.from(tickGen!);
+    expect(actual).toStrictEqual(expected);
+    expect(tickGen!.digits).toEqual(8);
+  });
 
-test('gridline helper to always return a reasonable number of steps', () => {
-  for (let i = 1; i <= 1000; i++) {
-    const [stepSize, _] = getStepSize(i, 14);
-    expect(Math.round(i / stepSize)).toBeGreaterThanOrEqual(6);
-    expect(Math.round(i / stepSize)).toBeLessThanOrEqual(14);
-  }
-});
+  it('can generate ticks when span has an offset', () => {
+    const tickGen = new TickGenerator(new TPTimeSpan(10n, 20n), 1);
+    const expected = [
+      {type: TickType.MAJOR, time: 10n},
+      {type: TickType.MINOR, time: 11n},
+      {type: TickType.MINOR, time: 12n},
+      {type: TickType.MINOR, time: 13n},
+      {type: TickType.MINOR, time: 14n},
+      {type: TickType.MEDIUM, time: 15n},
+      {type: TickType.MINOR, time: 16n},
+      {type: TickType.MINOR, time: 17n},
+      {type: TickType.MINOR, time: 18n},
+      {type: TickType.MINOR, time: 19n},
+    ];
+    const actual = Array.from(tickGen!);
+    expect(actual).toStrictEqual(expected);
+    expect(tickGen!.digits).toEqual(8);
+  });
 
-describe('TickGenerator with range 0.0-1.0 and room for 2 labels', () => {
-  let tickGen: TickGenerator|undefined = undefined;
-  beforeAll(() => {
-    const timeSpan = new TimeSpan(0.0, 1.0);
-    const timeScale = new TimeScale(timeSpan, [0, 200]);
-    tickGen = new TickGenerator(timeScale, {minLabelPx: 100});
-  });
-  it('should produce major ticks at 0.5s and minor ticks at 0.1s starting at 0',
-     () => {
-       const expected = [
-         {type: TickType.MAJOR, time: 0.0},
-         {type: TickType.MINOR, time: 0.1},
-         {type: TickType.MINOR, time: 0.2},
-         {type: TickType.MINOR, time: 0.3},
-         {type: TickType.MINOR, time: 0.4},
-         {type: TickType.MAJOR, time: 0.5},
-         {type: TickType.MINOR, time: 0.6},
-         {type: TickType.MINOR, time: 0.7},
-         {type: TickType.MINOR, time: 0.8},
-         {type: TickType.MINOR, time: 0.9},
-       ];
-       const actual = Array.from(tickGen!);
-       expectTicksEqual(actual, expected);
-     });
-  it('should tell us to use 1 decimal place for labels', () => {
-    expect(tickGen!.digits).toEqual(1);
-  });
-});
-
-describe('TickGenerator with range 0.3-1.3 and room for 2 labels', () => {
-  let tickGen: TickGenerator|undefined = undefined;
-  beforeAll(() => {
-    const timeSpan = new TimeSpan(0.3, 1.3);
-    const timeScale = new TimeScale(timeSpan, [0, 200]);
-    tickGen = new TickGenerator(timeScale, {minLabelPx: 100});
-  });
-  it('should produce major ticks at 0.5s and minor ticks at 0.1s starting at 0',
-     () => {
-       const expected = [
-         {type: TickType.MINOR, time: 0.3},
-         {type: TickType.MINOR, time: 0.4},
-         {type: TickType.MAJOR, time: 0.5},
-         {type: TickType.MINOR, time: 0.6},
-         {type: TickType.MINOR, time: 0.7},
-         {type: TickType.MINOR, time: 0.8},
-         {type: TickType.MINOR, time: 0.9},
-         {type: TickType.MAJOR, time: 1.0},
-         {type: TickType.MINOR, time: 1.1},
-         {type: TickType.MINOR, time: 1.2},
-       ];
-       const actual = Array.from(tickGen!);
-       expectTicksEqual(actual, expected);
-     });
-  it('should tell us to use 1 decimal place for labels', () => {
-    expect(tickGen!.digits).toEqual(1);
-  });
-});
-
-describe('TickGenerator with range 0.0-0.2 and room for 1 label', () => {
-  let tickGen: TickGenerator|undefined = undefined;
-  beforeAll(() => {
-    const timeSpan = new TimeSpan(0.0, 0.2);
-    const timeScale = new TimeScale(timeSpan, [0, 100]);
-    tickGen = new TickGenerator(timeScale, {minLabelPx: 100});
-  });
-  it('should produce major ticks at 0.2s and minor ticks at 0.1s starting at 0',
-     () => {
-       const expected = [
-         {type: TickType.MAJOR, time: 0.0},
-         {type: TickType.MINOR, time: 0.05},
-         {type: TickType.MEDIUM, time: 0.1},
-         {type: TickType.MINOR, time: 0.15},
-       ];
-       const actual = Array.from(tickGen!);
-       expectTicksEqual(actual, expected);
-     });
-  it('should tell us to use 1 decimal place for labels', () => {
-    expect(tickGen!.digits).toEqual(1);
-  });
-});
-
-describe('TickGenerator with range 0.0-0.1 and room for 1 label', () => {
-  let tickGen: TickGenerator|undefined = undefined;
-  beforeAll(() => {
-    const timeSpan = new TimeSpan(0.0, 0.1);
-    const timeScale = new TimeScale(timeSpan, [0, 100]);
-    tickGen = new TickGenerator(timeScale, {minLabelPx: 100});
-  });
-  it('should produce major ticks at 0.1s & minor ticks at 0.02s starting at 0',
-     () => {
-       const expected = [
-         {type: TickType.MAJOR, time: 0.0},
-         {type: TickType.MINOR, time: 0.01},
-         {type: TickType.MINOR, time: 0.02},
-         {type: TickType.MINOR, time: 0.03},
-         {type: TickType.MINOR, time: 0.04},
-         {type: TickType.MEDIUM, time: 0.05},
-         {type: TickType.MINOR, time: 0.06},
-         {type: TickType.MINOR, time: 0.07},
-         {type: TickType.MINOR, time: 0.08},
-         {type: TickType.MINOR, time: 0.09},
-       ];
-       const actual = Array.from(tickGen!);
-       expect(tickGen!.digits).toEqual(1);
-       expectTicksEqual(actual, expected);
-     });
-  it('should tell us to use 1 decimal place for labels', () => {
-    expect(tickGen!.digits).toEqual(1);
-  });
-});
-
-describe('TickGenerator with a very small timespan', () => {
-  let tickGen: TickGenerator|undefined = undefined;
-  beforeAll(() => {
-    const timeSpan = new TimeSpan(0.0, 1e-9);
-    const timeScale = new TimeScale(timeSpan, [0, 100]);
-    tickGen = new TickGenerator(timeScale, {minLabelPx: 100});
-  });
-  it('should generate minor ticks at 2e-10s and one major tick at the start',
-     () => {
-       const expected = [
-         {type: TickType.MAJOR, time: 0.0},
-         {type: TickType.MINOR, time: 1e-10},
-         {type: TickType.MINOR, time: 2e-10},
-         {type: TickType.MINOR, time: 3e-10},
-         {type: TickType.MINOR, time: 4e-10},
-         {type: TickType.MEDIUM, time: 5e-10},
-         {type: TickType.MINOR, time: 6e-10},
-         {type: TickType.MINOR, time: 7e-10},
-         {type: TickType.MINOR, time: 8e-10},
-         {type: TickType.MINOR, time: 9e-10},
-       ];
-       const actual = Array.from(tickGen!);
-       expectTicksEqual(actual, expected);
-     });
-  it('should tell us to use 9 decimal places for labels', () => {
-    expect(tickGen!.digits).toEqual(9);
-  });
-});
-
-describe('TickGenerator with a very large timespan', () => {
-  let tickGen: TickGenerator|undefined = undefined;
-  beforeAll(() => {
-    const timeSpan = new TimeSpan(0.0, 1e9);
-    const timeScale = new TimeScale(timeSpan, [0, 100]);
-    tickGen = new TickGenerator(timeScale, {minLabelPx: 100});
-  });
-  it('should generate minor ticks at 2e8 and one major tick at the start',
-     () => {
-       const expected = [
-         {type: TickType.MAJOR, time: 0.0},
-         {type: TickType.MINOR, time: 1e8},
-         {type: TickType.MINOR, time: 2e8},
-         {type: TickType.MINOR, time: 3e8},
-         {type: TickType.MINOR, time: 4e8},
-         {type: TickType.MEDIUM, time: 5e8},
-         {type: TickType.MINOR, time: 6e8},
-         {type: TickType.MINOR, time: 7e8},
-         {type: TickType.MINOR, time: 8e8},
-         {type: TickType.MINOR, time: 9e8},
-       ];
-       const actual = Array.from(tickGen!);
-       expectTicksEqual(actual, expected);
-     });
-  it('should tell us to use 0 decimal places for labels', () => {
+  it('can generate ticks when span is large', () => {
+    const tickGen =
+        new TickGenerator(new TPTimeSpan(1000000000n, 2000000000n), 1);
+    const expected = [
+      {type: TickType.MAJOR, time: 1000000000n},
+      {type: TickType.MINOR, time: 1100000000n},
+      {type: TickType.MINOR, time: 1200000000n},
+      {type: TickType.MINOR, time: 1300000000n},
+      {type: TickType.MINOR, time: 1400000000n},
+      {type: TickType.MEDIUM, time: 1500000000n},
+      {type: TickType.MINOR, time: 1600000000n},
+      {type: TickType.MINOR, time: 1700000000n},
+      {type: TickType.MINOR, time: 1800000000n},
+      {type: TickType.MINOR, time: 1900000000n},
+    ];
+    const actual = Array.from(tickGen!);
+    expect(actual).toStrictEqual(expected);
     expect(tickGen!.digits).toEqual(0);
   });
-});
 
-describe('TickGenerator where the timespan has a dynamic range of 1e12', () => {
-  // This is the equivalent of zooming in to the nanosecond level, 1000 seconds
-  // into a trace Note: this is about the limit of what this generator can
-  // handle.
-  let tickGen: TickGenerator|undefined = undefined;
-  beforeAll(() => {
-    const timeSpan = new TimeSpan(1000, 1000.000000001);
-    const timeScale = new TimeScale(timeSpan, [0, 100]);
-    tickGen = new TickGenerator(timeScale, {minLabelPx: 100});
+  it('throws an error when timespan duration is 0', () => {
+    expect(() => {
+      new TickGenerator(new TPTimeSpan(0n, 0n), 1);
+    }).toThrow(Error);
   });
-  it('should generate minor ticks at 1e-10s and one major tick at the start',
-     () => {
-       const expected = [
-         {type: TickType.MAJOR, time: 1000.0000000000},
-         {type: TickType.MINOR, time: 1000.0000000001},
-         {type: TickType.MINOR, time: 1000.0000000002},
-         {type: TickType.MINOR, time: 1000.0000000003},
-         {type: TickType.MINOR, time: 1000.0000000004},
-         {type: TickType.MEDIUM, time: 1000.0000000005},
-         {type: TickType.MINOR, time: 1000.0000000006},
-         {type: TickType.MINOR, time: 1000.0000000007},
-         {type: TickType.MINOR, time: 1000.0000000008},
-         {type: TickType.MINOR, time: 1000.0000000009},
-       ];
-       const actual = Array.from(tickGen!);
-       expectTicksEqual(actual, expected);
-     });
-  it('should tell us to use 9 decimal places for labels', () => {
-    expect(tickGen!.digits).toEqual(9);
+
+  it('throws an error when max ticks is 0', () => {
+    expect(() => {
+      new TickGenerator(new TPTimeSpan(0n, 1n), 0);
+    }).toThrow(Error);
   });
 });
-
-describe(
-    'TickGenerator where the timespan has a ridiculously huge dynamic range',
-    () => {
-      // We don't expect this to work, just wanna make sure it doesn't crash or
-      // get stuck
-      it('should not crash or get stuck in an infinite loop', () => {
-        const timeSpan = new TimeSpan(1000, 1000.000000000001);
-        const timeScale = new TimeScale(timeSpan, [0, 100]);
-        new TickGenerator(timeScale);
-      });
-    });
-
-describe(
-    'TickGenerator where the timespan has a ridiculously huge dynamic range',
-    () => {
-      // We don't expect this to work, just wanna make sure it doesn't crash or
-      // get stuck
-      it('should not crash or get stuck in an infinite loop', () => {
-        const timeSpan = new TimeSpan(1000, 1000.000000000001);
-        const timeScale = new TimeScale(timeSpan, [0, 100]);
-        new TickGenerator(timeScale);
-      });
-    });
-
-test('TickGenerator constructed with a 0 width throws an error', () => {
-  expect(() => {
-    const timeScale = new TimeScale(new TimeSpan(0.0, 1.0), [0, 0]);
-    new TickGenerator(timeScale);
-  }).toThrow(Error);
-});
-
-test(
-    'TickGenerator constructed with desiredPxPerStep of 0 throws an error',
-    () => {
-      expect(() => {
-        new TickGenerator(timeScale, {minLabelPx: 0});
-      }).toThrow(Error);
-    });
-
-test('TickGenerator constructed with a 0 duration throws an error', () => {
-  expect(() => {
-    const timeScale = new TimeScale(new TimeSpan(0.0, 0.0), [0, 1]);
-    new TickGenerator(timeScale);
-  }).toThrow(Error);
-});
-
-function expectTicksEqual(actual: Tick[], expected: any[]) {
-  // TODO(stevegolton) We could write a custom matcher for this; this approach
-  // produces cryptic error messages.
-  expect(actual.length).toEqual(expected.length);
-  for (let i = 0; i < actual.length; ++i) {
-    const ex = expected[i];
-    const ac = actual[i];
-    expect(ac.type).toEqual(ex.type);
-    expect(ac.time).toBeCloseTo(ex.time, 9);
-  }
-}
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 7da04de..f25bf52 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -14,6 +14,7 @@
 
 import {Actions} from '../common/actions';
 import {Area} from '../common/state';
+import {TPTime} from '../common/time';
 
 import {Flow, globals} from './globals';
 import {toggleHelp} from './help_modal';
@@ -23,7 +24,8 @@
 } from './scroll_helper';
 import {executeSearch} from './search_handler';
 
-const INSTANT_FOCUS_DURATION_S = 1 / 1e9;  // 1 ns.
+const INSTANT_FOCUS_DURATION = 1n;
+const INCOMPLETE_SLICE_DURATION = 30_000n;
 type Direction = 'Forward'|'Backward';
 
 // Handles all key events than are not handled by the
@@ -55,8 +57,8 @@
     if (selection !== null && selection.kind === 'AREA') {
       const area = globals.state.areas[selection.areaId];
       const coversEntireTimeRange =
-          globals.state.traceTime.startSec === area.startSec &&
-          globals.state.traceTime.endSec === area.endSec;
+          globals.state.traceTime.start === area.start &&
+          globals.state.traceTime.end === area.end;
       if (!coversEntireTimeRange) {
         // If the current selection is an area which does not cover the entire
         // time range, preserve the list of selected tracks and expand the time
@@ -71,10 +73,11 @@
       // If the current selection is not an area, select all.
       tracksToSelect = Object.keys(globals.state.tracks);
     }
+    const {start, end} = globals.state.traceTime;
     globals.dispatch(Actions.selectArea({
       area: {
-        startSec: globals.state.traceTime.startSec,
-        endSec: globals.state.traceTime.endSec,
+        start,
+        end,
         tracks: tracksToSelect,
       },
     }));
@@ -201,29 +204,29 @@
   }
 }
 
-function findTimeRangeOfSelection(): {startTs: number, endTs: number} {
+function findTimeRangeOfSelection(): {startTs: TPTime, endTs: TPTime} {
   const selection = globals.state.currentSelection;
-  let startTs = -1;
-  let endTs = -1;
+  let startTs = -1n;
+  let endTs = -1n;
   if (selection === null) {
     return {startTs, endTs};
   } else if (selection.kind === 'SLICE' || selection.kind === 'CHROME_SLICE') {
     const slice = globals.sliceDetails;
     if (slice.ts && slice.dur !== undefined && slice.dur > 0) {
-      startTs = slice.ts + globals.state.traceTime.startSec;
+      startTs = slice.ts;
       endTs = startTs + slice.dur;
     } else if (slice.ts) {
-      startTs = slice.ts + globals.state.traceTime.startSec;
+      startTs = slice.ts;
       // This will handle either:
       // a)slice.dur === -1 -> unfinished slice
       // b)slice.dur === 0  -> instant event
-      endTs = slice.dur === -1 ? globals.state.traceTime.endSec :
-                                 startTs + INSTANT_FOCUS_DURATION_S;
+      endTs = slice.dur === -1n ? startTs + INCOMPLETE_SLICE_DURATION :
+                                  startTs + INSTANT_FOCUS_DURATION;
     }
   } else if (selection.kind === 'THREAD_STATE') {
     const threadState = globals.threadStateDetails;
     if (threadState.ts && threadState.dur) {
-      startTs = threadState.ts + globals.state.traceTime.startSec;
+      startTs = threadState.ts;
       endTs = startTs + threadState.dur;
     }
   } else if (selection.kind === 'COUNTER') {
@@ -232,8 +235,8 @@
   } else if (selection.kind === 'AREA') {
     const selectedArea = globals.state.areas[selection.areaId];
     if (selectedArea) {
-      startTs = selectedArea.startSec;
-      endTs = selectedArea.endSec;
+      startTs = selectedArea.start;
+      endTs = selectedArea.end;
     }
   } else if (selection.kind === 'NOTE') {
     const selectedNote = globals.state.notes[selection.id];
@@ -241,16 +244,18 @@
     // above in the AREA case.
     if (selectedNote && selectedNote.noteType === 'DEFAULT') {
       startTs = selectedNote.timestamp;
-      endTs = selectedNote.timestamp + INSTANT_FOCUS_DURATION_S;
+      endTs = selectedNote.timestamp + INSTANT_FOCUS_DURATION;
     }
   } else if (selection.kind === 'LOG') {
     // TODO(hjd): Make focus selection work for logs.
-  } else if (selection.kind === 'DEBUG_SLICE') {
-    startTs = selection.startS;
-    if (selection.durationS > 0) {
-      endTs = startTs + selection.durationS;
+  } else if (
+      selection.kind === 'DEBUG_SLICE' ||
+      selection.kind === 'TOP_LEVEL_SCROLL') {
+    startTs = selection.start;
+    if (selection.duration > 0) {
+      endTs = startTs + selection.duration;
     } else {
-      endTs = startTs + INSTANT_FOCUS_DURATION_S;
+      endTs = startTs + INSTANT_FOCUS_DURATION;
     }
   }
 
@@ -260,12 +265,12 @@
 
 function lockSliceSpan(persistent = false) {
   const range = findTimeRangeOfSelection();
-  if (range.startTs !== -1 && range.endTs !== -1 &&
+  if (range.startTs !== -1n && range.endTs !== -1n &&
       globals.state.currentSelection !== null) {
     const tracks = globals.state.currentSelection.trackId ?
         [globals.state.currentSelection.trackId] :
         [];
-    const area: Area = {startSec: range.startTs, endSec: range.endTs, tracks};
+    const area: Area = {start: range.startTs, end: range.endTs, tracks};
     globals.dispatch(Actions.markArea({area, persistent}));
   }
 }
@@ -275,7 +280,7 @@
   if (selection === null) return;
 
   const range = findTimeRangeOfSelection();
-  if (range.startTs !== -1 && range.endTs !== -1) {
+  if (range.startTs !== -1n && range.endTs !== -1n) {
     focusHorizontalRange(range.startTs, range.endTs);
   }
 
diff --git a/ui/src/frontend/logs_panel.ts b/ui/src/frontend/logs_panel.ts
index 18ed325..88baa1c 100644
--- a/ui/src/frontend/logs_panel.ts
+++ b/ui/src/frontend/logs_panel.ts
@@ -16,14 +16,14 @@
 
 import {assertExists} from '../base/logging';
 import {Actions} from '../common/actions';
+import {HighPrecisionTimeSpan} from '../common/high_precision_time';
 import {
   LogBounds,
   LogBoundsKey,
   LogEntries,
   LogEntriesKey,
 } from '../common/logs';
-import {formatTimestamp} from '../common/time';
-import {TimeSpan} from '../common/time';
+import {formatTPTime, TPTime} from '../common/time';
 
 import {SELECTED_LOG_ROWS_COLOR} from './css_constants';
 import {globals} from './globals';
@@ -62,12 +62,16 @@
         dom.parentElement!.parentElement!.parentElement as HTMLElement);
     this.scrollContainer.addEventListener(
         'scroll', this.onScroll.bind(this), {passive: true});
+    // TODO(stevegolton): Type assersions are a source of bugs.
+    // Let's try to find another way of doing this.
     this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds;
     this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries;
     this.recomputeVisibleRowsAndUpdate();
   }
 
   onbeforeupdate(_: m.CVnodeDOM) {
+    // TODO(stevegolton): Type assersions are a source of bugs.
+    // Let's try to find another way of doing this.
     this.bounds = globals.trackDataStore.get(LogBoundsKey) as LogBounds;
     this.entries = globals.trackDataStore.get(LogEntriesKey) as LogEntries;
     this.recomputeVisibleRowsAndUpdate();
@@ -79,12 +83,12 @@
     globals.rafScheduler.scheduleFullRedraw();
   }
 
-  onRowOver(ts: number) {
+  onRowOver(ts: TPTime) {
     globals.dispatch(Actions.setHoverCursorTimestamp({ts}));
   }
 
   onRowOut() {
-    globals.dispatch(Actions.setHoverCursorTimestamp({ts: -1}));
+    globals.dispatch(Actions.setHoverCursorTimestamp({ts: -1n}));
   }
 
   private totalRows():
@@ -92,17 +96,19 @@
     if (!this.bounds) {
       return {isStale: false, total: 0, offset: 0, count: 0};
     }
-    const {total, startTs, endTs, firstRowTs, lastRowTs} = this.bounds;
+    const {
+      totalVisibleLogs,
+      firstVisibleLogTs,
+      lastVisibleLogTs,
+    } = this.bounds;
     const vis = globals.frontendLocalState.visibleWindowTime;
-    const leftSpan = new TimeSpan(startTs, firstRowTs);
-    const rightSpan = new TimeSpan(lastRowTs, endTs);
 
-    const isStaleLeft = !leftSpan.isInBounds(vis.start);
-    const isStaleRight = !rightSpan.isInBounds(vis.end);
-    const isStale = isStaleLeft || isStaleRight;
-    const offset = Math.min(this.visibleRowOffset, total);
-    const visCount = Math.min(total - offset, this.visibleRowCount);
-    return {isStale, total, count: visCount, offset};
+    const visibleLogSpan =
+        new HighPrecisionTimeSpan(firstVisibleLogTs, lastVisibleLogTs);
+    const isStale = !vis.contains(visibleLogSpan);
+    const offset = Math.min(this.visibleRowOffset, totalVisibleLogs);
+    const visCount = Math.min(totalVisibleLogs - offset, this.visibleRowCount);
+    return {isStale, total: totalVisibleLogs, count: visCount, offset};
   }
 
   view(_: m.CVnode<{}>) {
@@ -146,11 +152,10 @@
               {
                 'class': isStale ? 'stale' : '',
                 style,
-                'onmouseover': this.onRowOver.bind(this, ts / 1e9),
+                'onmouseover': this.onRowOver.bind(this, ts),
                 'onmouseout': this.onRowOut.bind(this),
               },
-              m('.cell',
-                formatTimestamp(ts / 1e9 - globals.state.traceTime.startSec)),
+              m('.cell', formatTPTime(ts - globals.state.traceTime.start)),
               m('.cell', priorityLetter || '?'),
               m('.cell', tags[i]),
               hasProcessNames ? m('.cell.with-process', processNames[i]) :
diff --git a/ui/src/frontend/notes_panel.ts b/ui/src/frontend/notes_panel.ts
index d3eec07..27d9ec8 100644
--- a/ui/src/frontend/notes_panel.ts
+++ b/ui/src/frontend/notes_panel.ts
@@ -17,7 +17,9 @@
 import {Actions} from '../common/actions';
 import {randomColor} from '../common/colorizer';
 import {AreaNote, Note} from '../common/state';
-import {timeToString} from '../common/time';
+import {
+  tpTimeToString,
+} from '../common/time';
 
 import {
   BottomTab,
@@ -28,6 +30,7 @@
 import {PerfettoMouseEvent} from './events';
 import {globals} from './globals';
 import {
+  getMaxMajorTicks,
   TickGenerator,
   TickType,
   timeScaleForVisibleWindow,
@@ -46,7 +49,7 @@
 
 function getStartTimestamp(note: Note|AreaNote) {
   if (note.noteType === 'AREA') {
-    return globals.state.areas[note.areaId].startSec;
+    return globals.state.areas[note.areaId].start;
   } else {
     return note.timestamp;
   }
@@ -66,7 +69,7 @@
     });
     dom.addEventListener('mouseout', () => {
       this.hoveredX = null;
-      globals.dispatch(Actions.setHoveredNoteTimestamp({ts: -1}));
+      globals.dispatch(Actions.setHoveredNoteTimestamp({ts: -1n}));
     }, {passive: true});
   }
 
@@ -110,15 +113,27 @@
   }
 
   renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
-    const timeScale = globals.frontendLocalState.timeScale;
     let aNoteIsHovered = false;
 
     ctx.fillStyle = '#999';
     ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height);
-    const relScale = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
-    if (relScale.timeSpan.duration > 0 && relScale.widthPx > 0) {
-      for (const {type, position} of new TickGenerator(relScale)) {
-        if (type === TickType.MAJOR) ctx.fillRect(position, 0, 1, size.height);
+
+    ctx.save();
+    ctx.beginPath();
+    ctx.rect(TRACK_SHELL_WIDTH, 0, size.width - TRACK_SHELL_WIDTH, size.height);
+    ctx.clip();
+
+    const span = globals.frontendLocalState.visibleWindow.timestampSpan;
+    const {visibleTimeScale} = globals.frontendLocalState;
+    if (size.width > TRACK_SHELL_WIDTH && span.duration > 0n) {
+      const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH);
+      const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
+      for (const {type, time} of new TickGenerator(
+               span, maxMajorTicks, globals.state.traceTime.start)) {
+        const px = Math.floor(map.tpTimeToPx(time));
+        if (type === TickType.MAJOR) {
+          ctx.fillRect(px, 0, 1, size.height);
+        }
       }
     }
 
@@ -129,11 +144,10 @@
       const timestamp = getStartTimestamp(note);
       // TODO(hjd): We should still render area selection marks in viewport is
       // *within* the area (e.g. both lhs and rhs are out of bounds).
-      if ((note.noteType !== 'AREA' && !timeScale.timeInBounds(timestamp)) ||
+      if ((note.noteType !== 'AREA' && !span.contains(timestamp)) ||
           (note.noteType === 'AREA' &&
-           !timeScale.timeInBounds(globals.state.areas[note.areaId].endSec) &&
-           !timeScale.timeInBounds(
-               globals.state.areas[note.areaId].startSec))) {
+           !span.contains(globals.state.areas[note.areaId].end) &&
+           !span.contains(globals.state.areas[note.areaId].start))) {
         continue;
       }
       const currentIsHovered =
@@ -144,7 +158,7 @@
       const isSelected = selection !== null &&
           ((selection.kind === 'NOTE' && selection.id === note.id) ||
            (selection.kind === 'AREA' && selection.noteId === note.id));
-      const x = timeScale.timeToPx(timestamp);
+      const x = visibleTimeScale.tpTimeToPx(timestamp);
       const left = Math.floor(x + TRACK_SHELL_WIDTH);
 
       // Draw flag or marker.
@@ -153,7 +167,8 @@
         this.drawAreaMarker(
             ctx,
             left,
-            Math.floor(timeScale.timeToPx(area.endSec) + TRACK_SHELL_WIDTH),
+            Math.floor(
+                visibleTimeScale.tpTimeToPx(area.end) + TRACK_SHELL_WIDTH),
             note.color,
             isSelected);
       } else {
@@ -175,19 +190,21 @@
     // A real note is hovered so we don't need to see the preview line.
     // TODO(hjd): Change cursor to pointer here.
     if (aNoteIsHovered) {
-      globals.dispatch(Actions.setHoveredNoteTimestamp({ts: -1}));
+      globals.dispatch(Actions.setHoveredNoteTimestamp({ts: -1n}));
     }
 
     // View preview note flag when hovering on notes panel.
     if (!aNoteIsHovered && this.hoveredX !== null) {
-      const timestamp = timeScale.pxToTime(this.hoveredX);
-      if (timeScale.timeInBounds(timestamp)) {
+      const timestamp = visibleTimeScale.pxToHpTime(this.hoveredX).toTPTime();
+      if (span.contains(timestamp)) {
         globals.dispatch(Actions.setHoveredNoteTimestamp({ts: timestamp}));
-        const x = timeScale.timeToPx(timestamp);
+        const x = visibleTimeScale.tpTimeToPx(timestamp);
         const left = Math.floor(x + TRACK_SHELL_WIDTH);
         this.drawFlag(ctx, left, size.height, '#aaa', /* fill */ true);
       }
     }
+
+    ctx.restore();
   }
 
   private drawAreaMarker(
@@ -197,7 +214,7 @@
     ctx.strokeStyle = color;
     const topOffset = 10;
     // Don't draw in the track shell section.
-    if (x >= globals.frontendLocalState.timeScale.startPx + TRACK_SHELL_WIDTH) {
+    if (x >= globals.frontendLocalState.windowSpan.start + TRACK_SHELL_WIDTH) {
       // Draw left triangle.
       ctx.beginPath();
       ctx.moveTo(x, topOffset);
@@ -218,7 +235,7 @@
 
     // Start line after track shell section, join triangles.
     const startDraw = Math.max(
-        x, globals.frontendLocalState.timeScale.startPx + TRACK_SHELL_WIDTH);
+        x, globals.frontendLocalState.windowSpan.start + TRACK_SHELL_WIDTH);
     ctx.beginPath();
     ctx.moveTo(startDraw, topOffset);
     ctx.lineTo(xEnd, topOffset);
@@ -250,8 +267,8 @@
 
   private onClick(x: number, _: number) {
     if (x < 0) return;
-    const timeScale = globals.frontendLocalState.timeScale;
-    const timestamp = timeScale.pxToTime(x);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const timestamp = visibleTimeScale.pxToHpTime(x).toTPTime();
     for (const note of Object.values(globals.state.notes)) {
       if (this.hoveredX && this.mouseOverNote(this.hoveredX, note)) {
         if (note.noteType === 'AREA') {
@@ -268,13 +285,13 @@
   }
 
   private mouseOverNote(x: number, note: AreaNote|Note): boolean {
-    const timeScale = globals.frontendLocalState.timeScale;
-    const noteX = timeScale.timeToPx(getStartTimestamp(note));
+    const timeScale = globals.frontendLocalState.visibleTimeScale;
+    const noteX = timeScale.tpTimeToPx(getStartTimestamp(note));
     if (note.noteType === 'AREA') {
       const noteArea = globals.state.areas[note.areaId];
       return (noteX <= x && x < noteX + AREA_TRIANGLE_WIDTH) ||
-          (timeScale.timeToPx(noteArea.endSec) > x &&
-           x > timeScale.timeToPx(noteArea.endSec) - AREA_TRIANGLE_WIDTH);
+          (timeScale.tpTimeToPx(noteArea.end) > x &&
+           x > timeScale.tpTimeToPx(noteArea.end) - AREA_TRIANGLE_WIDTH);
     } else {
       const width = FLAG_WIDTH;
       return noteX <= x && x < noteX + width;
@@ -308,13 +325,12 @@
     if (note === undefined) {
       return m('.', `No Note with id ${this.config.id}`);
     }
-    const startTime =
-        getStartTimestamp(note) - globals.state.traceTime.startSec;
+    const startTime = getStartTimestamp(note) - globals.state.traceTime.start;
     return m(
         '.notes-editor-panel',
         m('.notes-editor-panel-heading-bar',
           m('.notes-editor-panel-heading',
-            `Annotation at ${timeToString(startTime)}`),
+            `Annotation at ${tpTimeToString(startTime)}`),
           m('input[type=text]', {
             onkeydown: (e: Event) => {
               e.stopImmediatePropagation();
diff --git a/ui/src/frontend/overview_timeline_panel.ts b/ui/src/frontend/overview_timeline_panel.ts
index 76b4515..54f932e 100644
--- a/ui/src/frontend/overview_timeline_panel.ts
+++ b/ui/src/frontend/overview_timeline_panel.ts
@@ -14,9 +14,12 @@
 
 import m from 'mithril';
 
-import {assertExists} from '../base/logging';
 import {hueForCpu} from '../common/colorizer';
-import {TimeSpan} from '../common/time';
+import {
+  Span,
+  TPTime,
+  tpTimeToSeconds,
+} from '../common/time';
 
 import {
   OVERVIEW_TIMELINE_NON_VISIBLE_COLOR,
@@ -29,9 +32,9 @@
 import {OuterDragStrategy} from './drag/outer_drag_strategy';
 import {DragGestureHandler} from './drag_gesture_handler';
 import {globals} from './globals';
-import {TickGenerator, TickType} from './gridline_helper';
+import {getMaxMajorTicks, TickGenerator, TickType} from './gridline_helper';
 import {Panel, PanelSize} from './panel';
-import {TimeScale} from './time_scale';
+import {PxSpan, TimeScale} from './time_scale';
 
 export class OverviewTimelinePanel extends Panel {
   private static HANDLE_SIZE_PX = 5;
@@ -39,7 +42,7 @@
   private width = 0;
   private gesture?: DragGestureHandler;
   private timeScale?: TimeScale;
-  private totTime = new TimeSpan(0, 0);
+  private traceTime?: Span<TPTime>;
   private dragStrategy?: DragStrategy;
   private readonly boundOnMouseMove = this.onMouseMove.bind(this);
 
@@ -47,11 +50,11 @@
   // https://github.com/Microsoft/TypeScript/issues/1373
   onupdate({dom}: m.CVnodeDOM) {
     this.width = dom.getBoundingClientRect().width;
-    this.totTime = new TimeSpan(
-        globals.state.traceTime.startSec, globals.state.traceTime.endSec);
-    this.timeScale = new TimeScale(
-        this.totTime, [TRACK_SHELL_WIDTH, assertExists(this.width)]);
-
+    this.traceTime = globals.stateTraceTimeTP();
+    const traceTime = globals.stateTraceTime();
+    const pxSpan = new PxSpan(TRACK_SHELL_WIDTH, this.width);
+    this.timeScale =
+        new TimeScale(traceTime.start, traceTime.duration.nanos, pxSpan);
     if (this.gesture === undefined) {
       this.gesture = new DragGestureHandler(
           dom as HTMLElement,
@@ -78,26 +81,27 @@
 
   renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
     if (this.width === undefined) return;
+    if (this.traceTime === undefined) return;
     if (this.timeScale === undefined) return;
     const headerHeight = 20;
     const tracksHeight = size.height - headerHeight;
-    const timeSpan = new TimeSpan(0, this.totTime.duration);
 
-    const timeScale = new TimeScale(timeSpan, [TRACK_SHELL_WIDTH, this.width]);
-
-    if (timeScale.timeSpan.duration > 0 && timeScale.widthPx > 0) {
-      const tickGen = new TickGenerator(timeScale);
+    if (size.width > TRACK_SHELL_WIDTH && this.traceTime.duration > 0n) {
+      const maxMajorTicks = getMaxMajorTicks(this.width - TRACK_SHELL_WIDTH);
+      const tickGen = new TickGenerator(
+          this.traceTime, maxMajorTicks, globals.state.traceTime.start);
 
       // Draw time labels on the top header.
       ctx.font = '10px Roboto Condensed';
       ctx.fillStyle = '#999';
-      for (const {type, time, position} of tickGen) {
-        const xPos = Math.round(position);
+      for (const {type, time} of tickGen) {
+        const xPos = Math.floor(this.timeScale.tpTimeToPx(time));
         if (xPos <= 0) continue;
         if (xPos > this.width) break;
         if (type === TickType.MAJOR) {
           ctx.fillRect(xPos - 1, 0, 1, headerHeight - 5);
-          ctx.fillText(time.toFixed(tickGen.digits) + ' s', xPos + 5, 18);
+          const sec = tpTimeToSeconds(time - globals.state.traceTime.start);
+          ctx.fillText(sec.toFixed(tickGen.digits) + ' s', xPos + 5, 18);
         } else if (type == TickType.MEDIUM) {
           ctx.fillRect(xPos - 1, 0, 1, 8);
         } else if (type == TickType.MINOR) {
@@ -114,8 +118,8 @@
       for (const key of globals.overviewStore.keys()) {
         const loads = globals.overviewStore.get(key)!;
         for (let i = 0; i < loads.length; i++) {
-          const xStart = Math.floor(this.timeScale.timeToPx(loads[i].startSec));
-          const xEnd = Math.ceil(this.timeScale.timeToPx(loads[i].endSec));
+          const xStart = Math.floor(this.timeScale.tpTimeToPx(loads[i].start));
+          const xEnd = Math.ceil(this.timeScale.tpTimeToPx(loads[i].end));
           const yOff = Math.floor(headerHeight + y * trackHeight);
           const lightness = Math.ceil((1 - loads[i].load * 0.7) * 100);
           ctx.fillStyle = `hsl(${hueForCpu(y)}, 50%, ${lightness}%)`;
@@ -210,10 +214,10 @@
   }
 
   private static extractBounds(timeScale: TimeScale): [number, number] {
-    const vizTime = globals.frontendLocalState.getVisibleStateBounds();
+    const vizTime = globals.frontendLocalState.visibleWindowTime;
     return [
-      Math.floor(timeScale.timeToPx(vizTime[0])),
-      Math.ceil(timeScale.timeToPx(vizTime[1])),
+      Math.floor(timeScale.hpTimeToPx(vizTime.start)),
+      Math.ceil(timeScale.hpTimeToPx(vizTime.end)),
     ];
   }
 
diff --git a/ui/src/frontend/panel_container.ts b/ui/src/frontend/panel_container.ts
index 4c6576f..b7841d7 100644
--- a/ui/src/frontend/panel_container.ts
+++ b/ui/src/frontend/panel_container.ts
@@ -135,11 +135,13 @@
       return;
     }
 
+    const {visibleTimeScale} = globals.frontendLocalState;
+
     // The Y value is given from the top of the pan and zoom region, we want it
     // from the top of the panel container. The parent offset corrects that.
     const panels = this.getPanelsInRegion(
-        globals.frontendLocalState.timeScale.timeToPx(area.startSec),
-        globals.frontendLocalState.timeScale.timeToPx(area.endSec),
+        visibleTimeScale.tpTimeToPx(area.start),
+        visibleTimeScale.tpTimeToPx(area.end),
         globals.frontendLocalState.areaY.start + TOPBAR_HEIGHT,
         globals.frontendLocalState.areaY.end + TOPBAR_HEIGHT);
     // Get the track ids from the panels.
@@ -160,7 +162,7 @@
         }
       }
     }
-    globals.frontendLocalState.selectArea(area.startSec, area.endSec, tracks);
+    globals.frontendLocalState.selectArea(area.start, area.end, tracks);
   }
 
   constructor(vnode: m.CVnode<Attrs>) {
@@ -449,8 +451,9 @@
       return;
     }
 
-    const startX = globals.frontendLocalState.timeScale.timeToPx(area.startSec);
-    const endX = globals.frontendLocalState.timeScale.timeToPx(area.endSec);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const startX = visibleTimeScale.tpTimeToPx(area.start);
+    const endX = visibleTimeScale.tpTimeToPx(area.end);
     // To align with where to draw on the canvas subtract the first panel Y.
     selectedTracksMinY -= this.panelContainerTop;
     selectedTracksMaxY -= this.panelContainerTop;
diff --git a/ui/src/frontend/pivot_table_query_generator.ts b/ui/src/frontend/pivot_table_query_generator.ts
index 0c61f56..dffa6e4 100644
--- a/ui/src/frontend/pivot_table_query_generator.ts
+++ b/ui/src/frontend/pivot_table_query_generator.ts
@@ -20,7 +20,6 @@
   PivotTableQuery,
   PivotTableState,
 } from '../common/state';
-import {toNs} from '../common/time';
 import {
   getSelectedTrackIds,
 } from '../controller/aggregation/slice_aggregation_controller';
@@ -100,8 +99,8 @@
 
 export function areaFilter(area: Area): string {
   return `
-    ts + dur > ${toNs(area.startSec)}
-    and ts < ${toNs(area.endSec)}
+    ts + dur > ${area.start}
+    and ts < ${area.end}
     and track_id in (${getSelectedTrackIds(area).join(', ')})
   `;
 }
diff --git a/ui/src/frontend/publish.ts b/ui/src/frontend/publish.ts
index 7632e53..0cc5604 100644
--- a/ui/src/frontend/publish.ts
+++ b/ui/src/frontend/publish.ts
@@ -54,6 +54,11 @@
   globals.rafScheduler.scheduleRedraw();
 }
 
+export function clearOverviewData() {
+  globals.overviewStore.clear();
+  globals.rafScheduler.scheduleRedraw();
+}
+
 export function publishTrackData(args: {id: string, data: {}}) {
   globals.setTrackData(args.id, args.data);
   if ([LogExistsKey, LogBoundsKey, LogEntriesKey].includes(args.id)) {
diff --git a/ui/src/frontend/query_table.ts b/ui/src/frontend/query_table.ts
index c27ecc2..a58d1d6 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -14,13 +14,15 @@
 
 
 import m from 'mithril';
+import {BigintMath} from '../base/bigint_math';
 
 import {Actions} from '../common/actions';
 import {QueryResponse} from '../common/queries';
 import {ColumnType, Row} from '../common/query_result';
-import {fromNs} from '../common/time';
-import {Anchor} from './anchor';
+import {TPTime, tpTimeFromNanos} from '../common/time';
+import {TPDuration} from '../common/time';
 
+import {Anchor} from './anchor';
 import {copyToClipboard, queryResponseToClipboard} from './clipboard';
 import {downloadData} from './download_utils';
 import {globals} from './globals';
@@ -38,6 +40,16 @@
 }
 
 // Convert column value to number if it's a bigint or a number, otherwise throw
+function colToTimestamp(colValue: ColumnType): TPTime {
+  if (typeof colValue === 'bigint') {
+    return colValue;
+  } else if (typeof colValue === 'number') {
+    return tpTimeFromNanos(colValue);
+  } else {
+    throw Error('Value is not a number or a bigint');
+  }
+}
+
 function colToNumber(colValue: ColumnType): number {
   if (typeof colValue === 'bigint') {
     return Number(colValue);
@@ -48,6 +60,15 @@
   }
 }
 
+function colToDuration(colValue: ColumnType): TPDuration {
+  return colToTimestamp(colValue);
+}
+
+function clampDurationLower(
+    dur: TPDuration, lowerClamp: TPDuration): TPDuration {
+  return BigintMath.max(dur, lowerClamp);
+}
+
 class QueryTableRow implements m.ClassComponent<QueryTableRowAttrs> {
   static columnsContainsSliceLocation(columns: string[]) {
     const requiredColumns = ['ts', 'dur', 'track_id'];
@@ -65,15 +86,14 @@
     // the slice.
     event.stopPropagation();
 
-    const sliceStart = fromNs(colToNumber(row.ts));
+    const sliceStart = colToTimestamp(row.ts);
     // row.dur can be negative. Clamp to 1ns.
-    const sliceDur = fromNs(Math.max(colToNumber(row.dur), 1));
+    const sliceDur = clampDurationLower(colToDuration(row.dur), 1n);
     const sliceEnd = sliceStart + sliceDur;
-    const trackId: number = colToNumber(row.track_id);
+    const trackId = colToNumber(row.track_id);
     const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
     if (uiTrackId === undefined) return;
     verticalScrollToTrack(uiTrackId, true);
-    // TODO(stevegolton) Soon this function will only accept Bigints
     focusHorizontalRange(sliceStart, sliceEnd);
 
     let sliceId: number|undefined;
diff --git a/ui/src/frontend/scroll_helper.ts b/ui/src/frontend/scroll_helper.ts
index 18a9a78..f177a65 100644
--- a/ui/src/frontend/scroll_helper.ts
+++ b/ui/src/frontend/scroll_helper.ts
@@ -13,23 +13,28 @@
 // limitations under the License.
 
 import {Actions} from '../common/actions';
+import {
+  HighPrecisionTime,
+  HighPrecisionTimeSpan,
+} from '../common/high_precision_time';
 import {getContainingTrackId} from '../common/state';
-import {fromNs, TimeSpan, toNs} from '../common/time';
+import {TPTime} from '../common/time';
 
 import {globals} from './globals';
 
-const INCOMPLETE_SLICE_TIME_S = 0.00003;
 
 // Given a timestamp, if |ts| is not currently in view move the view to
 // center |ts|, keeping the same zoom level.
-export function horizontalScrollToTs(ts: number) {
-  const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
-  const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
-  const currentViewNs = endNs - startNs;
-  if (ts < startNs || ts > endNs) {
+// TODO(stevegolton): Remove me!
+export function horizontalScrollToTs(ts: TPTime) {
+  console.log('horizontalScrollToTs', ts);
+  const time = HighPrecisionTime.fromTPTime(ts);
+  const {start, end, duration} = globals.frontendLocalState.visibleWindowTime;
+  const halfDuration = duration.nanos / 2;
+  if (time.isLessThan(start) || time.isGreaterThan(end)) {
     // TODO(hjd): This is an ugly jump, we should do a smooth pan instead.
-    globals.frontendLocalState.updateVisibleTime(new TimeSpan(
-        fromNs(ts - currentViewNs / 2), fromNs(ts + currentViewNs / 2)));
+    globals.frontendLocalState.updateVisibleTime(new HighPrecisionTimeSpan(
+        time.subtractNanos(halfDuration), time.addNanos(halfDuration)));
   }
 }
 
@@ -46,16 +51,11 @@
 //   to cover 1/5 of the viewport.
 // - Otherwise, preserve the zoom range.
 export function focusHorizontalRange(
-    startTs: number, endTs: number, viewPercentage?: number) {
-  const visibleDur = globals.frontendLocalState.visibleWindowTime.end -
-      globals.frontendLocalState.visibleWindowTime.start;
-  let selectDur = endTs - startTs;
-  // TODO(altimin): We go from `ts` and `dur` to `startTs` and `endTs` and back
-  // to `dur`. We should fix that.
-  if (toNs(selectDur) === -1) {  // Unfinished slice
-    selectDur = INCOMPLETE_SLICE_TIME_S;
-    endTs = startTs;
-  }
+    start: TPTime, end: TPTime, viewPercentage?: number) {
+  console.log('focusHorizontalRange', start, end);
+  const visible = globals.frontendLocalState.visibleWindowTime;
+  const trace = globals.stateTraceTime();
+  const select = HighPrecisionTimeSpan.fromTpTime(start, end);
 
   if (viewPercentage !== undefined) {
     if (viewPercentage <= 0.0 || viewPercentage > 1.0) {
@@ -67,51 +67,43 @@
       viewPercentage = 0.5;
     }
     const paddingPercentage = 1.0 - viewPercentage;
-    const paddingTime = selectDur * paddingPercentage;
-    const halfPaddingTime = paddingTime / 2;
-    globals.frontendLocalState.updateVisibleTime(
-        new TimeSpan(startTs - halfPaddingTime, endTs + halfPaddingTime));
+    const paddingTime = select.duration.multiply(paddingPercentage);
+    const halfPaddingTime = paddingTime.divide(2);
+    globals.frontendLocalState.updateVisibleTime(select.pad(halfPaddingTime));
     return;
   }
-
   // If the range is too large to fit on the current zoom level, resize.
-  if (selectDur > 0.5 * visibleDur) {
-    globals.frontendLocalState.updateVisibleTime(
-        new TimeSpan(startTs - (selectDur * 2), endTs + (selectDur * 2)));
+  if (select.duration.isGreaterThan(visible.duration.multiply(0.5))) {
+    const paddedRange = select.pad(select.duration.multiply(2));
+    globals.frontendLocalState.updateVisibleTime(paddedRange);
     return;
   }
-  const midpointTs = (endTs + startTs) / 2;
   // Calculate the new visible window preserving the zoom level.
-  let newStartTs = midpointTs - visibleDur / 2;
-  let newEndTs = midpointTs + visibleDur / 2;
+  let newStart = select.midpoint.subtract(visible.duration.divide(2));
+  let newEnd = select.midpoint.add(visible.duration.divide(2));
 
   // Adjust the new visible window if it intersects with the trace boundaries.
   // It's needed to make the "update the zoom level if visible window doesn't
   // change" logic reliable.
-  if (newEndTs > globals.state.traceTime.endSec) {
-    newStartTs = globals.state.traceTime.endSec - visibleDur;
-    newEndTs = globals.state.traceTime.endSec;
+  if (newEnd.isGreaterThan(trace.end)) {
+    newStart = trace.end.subtract(visible.duration);
+    newEnd = trace.end;
   }
-  if (newStartTs < globals.state.traceTime.startSec) {
-    newStartTs = globals.state.traceTime.startSec;
-    newEndTs = globals.state.traceTime.startSec + visibleDur;
+  if (newStart.isLessThan(trace.start)) {
+    newStart = trace.start;
+    newEnd = trace.start.add(visible.duration);
   }
 
-  const newStartNs = toNs(newStartTs);
-  const newEndNs = toNs(newEndTs);
-
-  const viewStartNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
-  const viewEndNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
+  const view = new HighPrecisionTimeSpan(newStart, newEnd);
 
   // If preserving the zoom doesn't change the visible window, update the zoom
   // level.
-  if (newStartNs === viewStartNs && newEndNs === viewEndNs) {
-    globals.frontendLocalState.updateVisibleTime(
-        new TimeSpan(startTs - (selectDur * 2), endTs + (selectDur * 2)));
-    return;
+  if (view.start.equals(visible.start) && view.end.equals(visible.end)) {
+    const padded = select.pad(select.duration.multiply(2));
+    globals.frontendLocalState.updateVisibleTime(padded);
+  } else {
+    globals.frontendLocalState.updateVisibleTime(view);
   }
-  globals.frontendLocalState.updateVisibleTime(
-      new TimeSpan(newStartTs, newEndTs));
 }
 
 // Given a track id, find a track with that id and scroll it into view. If the
@@ -155,7 +147,7 @@
 
 // Scroll vertically and horizontally to reach track (|trackId|) at |ts|.
 export function scrollToTrackAndTs(
-    trackId: string|number|undefined, ts: number, openGroup = false) {
+    trackId: string|number|undefined, ts: TPTime, openGroup = false) {
   if (trackId !== undefined) {
     verticalScrollToTrack(trackId, openGroup);
   }
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
index 1622a7e..f995617 100644
--- a/ui/src/frontend/search_handler.ts
+++ b/ui/src/frontend/search_handler.ts
@@ -14,8 +14,6 @@
 
 import {searchSegment} from '../base/binary_search';
 import {Actions} from '../common/actions';
-import {toNs} from '../common/time';
-
 import {globals} from './globals';
 
 function setToPrevious(current: number) {
@@ -34,8 +32,9 @@
 
 export function executeSearch(reverse = false) {
   const index = globals.state.searchIndex;
-  const startNs = toNs(globals.frontendLocalState.visibleWindowTime.start);
-  const endNs = toNs(globals.frontendLocalState.visibleWindowTime.end);
+  const vizWindow = globals.frontendLocalState.visibleWindowTime;
+  const startNs = vizWindow.start.nanos;
+  const endNs = vizWindow.end.nanos;
   const currentTs = globals.currentSearchResults.tsStarts[index];
 
   // If the value of |globals.currentSearchResults.totalResults| is 0,
diff --git a/ui/src/frontend/slice.ts b/ui/src/frontend/slice.ts
index 5b660ef..7587a27 100644
--- a/ui/src/frontend/slice.ts
+++ b/ui/src/frontend/slice.ts
@@ -13,13 +13,14 @@
 // limitations under the License.
 
 import {Color} from '../common/colorizer';
+import {TPDuration, TPTime} from '../common/time';
 
 export interface Slice {
   // These properties are updated only once per query result when the Slice
   // object is created and don't change afterwards.
   readonly id: number;
-  readonly startS: number;
-  readonly durationS: number;
+  readonly start: TPTime;
+  readonly duration: TPDuration;
   readonly depth: number;
   readonly flags: number;
 
diff --git a/ui/src/frontend/slice_details_panel.ts b/ui/src/frontend/slice_details_panel.ts
index 9b018dd..13e3dd5 100644
--- a/ui/src/frontend/slice_details_panel.ts
+++ b/ui/src/frontend/slice_details_panel.ts
@@ -16,7 +16,7 @@
 
 import {Actions} from '../common/actions';
 import {translateState} from '../common/thread_state';
-import {timeToCode, toNs} from '../common/time';
+import {tpTimeToCode} from '../common/time';
 import {globals, SliceDetails, ThreadDesc} from './globals';
 import {scrollToTrackAndTs} from './scroll_helper';
 import {SlicePanel} from './slice_panel';
@@ -60,9 +60,8 @@
     if (!threadInfo) {
       return null;
     }
-    const timestamp = timeToCode(
-        sliceInfo.wakeupTs! - globals.state.traceTime.startSec,
-    );
+    const timestamp =
+        tpTimeToCode(sliceInfo.wakeupTs! - globals.state.traceTime.start);
     return m(
         '.slice-details-wakeup-text',
         m('', `Wakeup @ ${timestamp} on CPU ${sliceInfo.wakerCpu} by`),
@@ -76,9 +75,7 @@
       return null;
     }
 
-    const latency = timeToCode(
-        sliceInfo.ts - (sliceInfo.wakeupTs - globals.state.traceTime.startSec),
-    );
+    const latency = tpTimeToCode(sliceInfo.ts - sliceInfo.wakeupTs);
     return m(
         '.slice-details-latency-text',
         m('', `Scheduling latency: ${latency}`),
@@ -111,7 +108,10 @@
               {onclick: () => this.goToThread(), title: 'Go to thread'},
               'call_made'))),
         m('tr', m('th', `Cmdline`), m('td', threadInfo.cmdline)),
-        m('tr', m('th', `Start time`), m('td', `${timeToCode(sliceInfo.ts)}`)),
+        m('tr',
+          m('th', `Start time`),
+          m('td',
+            `${tpTimeToCode(sliceInfo.ts - globals.state.traceTime.start)}`)),
         m('tr',
           m('th', `Duration`),
           m('td', this.computeDuration(sliceInfo.ts, sliceInfo.dur))),
@@ -172,8 +172,7 @@
         trackId: trackId.toString(),
       }));
 
-      scrollToTrackAndTs(
-          trackId, toNs(sliceInfo.ts + globals.state.traceTime.startSec), true);
+      scrollToTrackAndTs(trackId, sliceInfo.ts, true);
     }
   }
 
diff --git a/ui/src/frontend/slice_panel.ts b/ui/src/frontend/slice_panel.ts
index 17b4aeb..9d6f542 100644
--- a/ui/src/frontend/slice_panel.ts
+++ b/ui/src/frontend/slice_panel.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {timeToCode, toNs} from '../common/time';
+import {TPDuration, TPTime, tpTimeToCode} from '../common/time';
 
 import {globals, SliceDetails} from './globals';
 import {Panel} from './panel';
@@ -34,10 +34,9 @@
 }
 
 export abstract class SlicePanel extends Panel {
-  protected computeDuration(ts: number, dur: number): string {
-    return toNs(dur) === -1 ?
-        `${globals.state.traceTime.endSec - ts} (Did not end)` :
-        timeToCode(dur);
+  protected computeDuration(ts: TPTime, dur: TPDuration): string {
+    return dur === -1n ? `${globals.state.traceTime.end - ts} (Did not end)` :
+                         tpTimeToCode(dur);
   }
 
   protected getProcessThreadDetails(sliceInfo: SliceDetails) {
diff --git a/ui/src/frontend/sql_types.ts b/ui/src/frontend/sql_types.ts
index f7135fa..b7e2df2 100644
--- a/ui/src/frontend/sql_types.ts
+++ b/ui/src/frontend/sql_types.ts
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {ColumnType} from 'src/common/query_result';
-import {fromNs, toNs} from '../common/time';
+import {TPTime} from '../common/time';
+
 import {globals} from './globals';
 
 // Type-safe aliases for various flavours of ints Trace Processor exposes
@@ -26,37 +26,19 @@
 
 // Timestamp (in nanoseconds) in the same time domain as Trace Processor is
 // exposing.
-export type TPTimestamp = bigint&{
+export type TPTimestamp = TPTime&{
   __type: 'TPTimestamp'
 }
 
-// Create a timestamp from a bigint in nanos.
-// Use this when we know the type is a bigint.
-export function timestampFromNanos(nanos: bigint) {
-  return nanos as TPTimestamp;
-}
-
-// Create a timestamp from an arbitrary SQL value.
-// Throws if the value cannot be reasonably converted to a timestamp.
-// Assumes the input will be in units of nanoseconds.
-export function timestampFromSqlNanos(nanos: ColumnType): TPTimestamp {
-  if (typeof nanos === 'bigint') {
-    return nanos as TPTimestamp;
-  } else if (typeof nanos === 'number') {
-    // Note - this will throw if the number is something which cannot be
-    // represented by an integer - i.e. decimals, infinity, or NaN.
-    return BigInt(nanos) as TPTimestamp;
-  } else {
-    throw Error('Refusing to create TPTimestamp from unrelated type');
-  }
+export function asTPTimestamp(v: bigint): TPTimestamp;
+export function asTPTimestamp(v?: bigint): TPTimestamp|undefined;
+export function asTPTimestamp(v?: bigint): TPTimestamp|undefined {
+  return v as (TPTimestamp | undefined);
 }
 
 // TODO: unify this with common/time.ts.
-// TODO(stevegolton): Return a bigint, or a new TPDuration object rather than
-// convert to number which could lose precision.
-export function toTraceTime(ts: TPTimestamp): number {
-  const traceStartNs = toNs(globals.state.traceTime.startSec);
-  return fromNs(Number(ts - BigInt(traceStartNs)));
+export function toTraceTime(ts: TPTimestamp): TPTime {
+  return ts - globals.state.traceTime.start;
 }
 
 // Unique id for a process, id into |process| table.
diff --git a/ui/src/frontend/thread_state.ts b/ui/src/frontend/thread_state.ts
index c7031f2..0bc4082 100644
--- a/ui/src/frontend/thread_state.ts
+++ b/ui/src/frontend/thread_state.ts
@@ -16,7 +16,11 @@
 import {EngineProxy} from '../common/engine';
 import {LONG, NUM, NUM_NULL, STR_NULL} from '../common/query_result';
 import {translateState} from '../common/thread_state';
-import {fromNs, timeToCode} from '../common/time';
+import {
+  TPDuration,
+  TPTime,
+  tpTimeToCode,
+} from '../common/time';
 
 import {copyToClipboard} from './clipboard';
 import {globals} from './globals';
@@ -26,9 +30,6 @@
   asUtid,
   SchedSqlId,
   ThreadStateSqlId,
-  timestampFromNanos,
-  toTraceTime,
-  TPTimestamp,
 } from './sql_types';
 import {
   constraintsToQueryFragment,
@@ -50,10 +51,10 @@
   threadStateSqlId: ThreadStateSqlId;
   // Id of the corresponding entry in the |sched| table.
   schedSqlId?: SchedSqlId;
-  // Timestamp of the beginning of this thread state in nanoseconds.
-  ts: TPTimestamp;
+  // Timestamp of the the beginning of this thread state in nanoseconds.
+  ts: TPTime;
   // Duration of this thread state in nanoseconds.
-  dur: number;
+  dur: TPDuration;
   // CPU id if this thread state corresponds to a thread running on the CPU.
   cpu?: number;
   // Human-readable name of this thread state.
@@ -90,7 +91,7 @@
     threadStateSqlId: NUM,
     schedSqlId: NUM_NULL,
     ts: LONG,
-    dur: NUM,
+    dur: LONG,
     cpu: NUM_NULL,
     state: STR_NULL,
     blockedFunction: STR_NULL,
@@ -110,7 +111,7 @@
     result.push({
       threadStateSqlId: it.threadStateSqlId as ThreadStateSqlId,
       schedSqlId: fromNumNull(it.schedSqlId) as (SchedSqlId | undefined),
-      ts: timestampFromNanos(it.ts),
+      ts: it.ts,
       dur: it.dur,
       cpu: fromNumNull(it.cpu),
       state: translateState(it.state || undefined, ioWait),
@@ -137,7 +138,7 @@
   return result[0];
 }
 
-export function goToSchedSlice(cpu: number, id: SchedSqlId, ts: TPTimestamp) {
+export function goToSchedSlice(cpu: number, id: SchedSqlId, ts: TPTime) {
   let trackId: string|undefined;
   for (const track of Object.values(globals.state.tracks)) {
     if (track.kind === 'CpuSliceTrack' &&
@@ -149,15 +150,12 @@
     return;
   }
   globals.makeSelection(Actions.selectSlice({id, trackId}));
-  // TODO(stevegolton): scrollToTrackAndTs() should take a TPTimestamp
-  scrollToTrackAndTs(trackId, Number(ts));
+  scrollToTrackAndTs(trackId, ts);
 }
 
 function stateToValue(
-    state: string,
-    cpu: number|undefined,
-    id: SchedSqlId|undefined,
-    ts: TPTimestamp): Value|null {
+    state: string, cpu: number|undefined, id: SchedSqlId|undefined, ts: TPTime):
+    Value|null {
   if (!state) {
     return null;
   }
@@ -177,8 +175,9 @@
 export function threadStateToDict(state: ThreadState): Dict {
   const result: {[name: string]: Value|null} = {};
 
-  result['Start time'] = value(timeToCode(toTraceTime(state.ts)));
-  result['Duration'] = value(timeToCode(fromNs(state.dur)));
+  result['Start time'] =
+      value(tpTimeToCode(state.ts - globals.state.traceTime.start));
+  result['Duration'] = value(tpTimeToCode(state.dur));
   result['State'] =
       stateToValue(state.state, state.cpu, state.schedSqlId, state.ts);
   result['Blocked function'] = maybeValue(state.blockedFunction);
diff --git a/ui/src/frontend/tickmark_panel.ts b/ui/src/frontend/tickmark_panel.ts
index 00612eb..f1579b9 100644
--- a/ui/src/frontend/tickmark_panel.ts
+++ b/ui/src/frontend/tickmark_panel.ts
@@ -19,6 +19,7 @@
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {globals} from './globals';
 import {
+  getMaxMajorTicks,
   TickGenerator,
   TickType,
   timeScaleForVisibleWindow,
@@ -32,14 +33,26 @@
   }
 
   renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {visibleWindowTime, visibleTimeScale} = globals.frontendLocalState;
 
     ctx.fillStyle = '#999';
     ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height);
-    const relScale = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
-    if (relScale.timeSpan.duration > 0 && relScale.widthPx > 0) {
-      for (const {type, position} of new TickGenerator(relScale)) {
-        if (type === TickType.MAJOR) ctx.fillRect(position, 0, 1, size.height);
+
+    ctx.save();
+    ctx.beginPath();
+    ctx.rect(TRACK_SHELL_WIDTH, 0, size.width - TRACK_SHELL_WIDTH, size.height);
+    ctx.clip();
+
+    const span = globals.frontendLocalState.visibleWindow.timestampSpan;
+    if (size.width > TRACK_SHELL_WIDTH && span.duration > 0n) {
+      const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH);
+      const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
+      for (const {type, time} of new TickGenerator(
+               span, maxMajorTicks, globals.state.traceTime.start)) {
+        const px = Math.floor(map.tpTimeToPx(time));
+        if (type === TickType.MAJOR) {
+          ctx.fillRect(px, 0, 1, size.height);
+        }
       }
     }
 
@@ -47,12 +60,13 @@
     for (let i = 0; i < data.tsStarts.length; i++) {
       const tStart = data.tsStarts[i];
       const tEnd = data.tsEnds[i];
-      if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
+      if (tEnd <= visibleWindowTime.start.seconds ||
+          tStart >= visibleWindowTime.end.seconds) {
         continue;
       }
       const rectStart =
-          Math.max(timeScale.timeToPx(tStart), 0) + TRACK_SHELL_WIDTH;
-      const rectEnd = timeScale.timeToPx(tEnd) + TRACK_SHELL_WIDTH;
+          Math.max(visibleTimeScale.secondsToPx(tStart), 0) + TRACK_SHELL_WIDTH;
+      const rectEnd = visibleTimeScale.secondsToPx(tEnd) + TRACK_SHELL_WIDTH;
       ctx.fillStyle = '#ffe263';
       ctx.fillRect(
           Math.floor(rectStart),
@@ -61,16 +75,21 @@
           size.height);
     }
     const index = globals.state.searchIndex;
-    const startSec = fromNs(globals.currentSearchResults.tsStarts[index]);
-    const triangleStart =
-        Math.max(timeScale.timeToPx(startSec), 0) + TRACK_SHELL_WIDTH;
-    ctx.fillStyle = '#000';
-    ctx.beginPath();
-    ctx.moveTo(triangleStart, size.height);
-    ctx.lineTo(triangleStart - 3, 0);
-    ctx.lineTo(triangleStart + 3, 0);
-    ctx.lineTo(triangleStart, size.height);
-    ctx.fill();
-    ctx.closePath();
+    if (index !== -1) {
+      const startSec = fromNs(globals.currentSearchResults.tsStarts[index]);
+      const triangleStart =
+          Math.max(visibleTimeScale.secondsToPx(startSec), 0) +
+          TRACK_SHELL_WIDTH;
+      ctx.fillStyle = '#000';
+      ctx.beginPath();
+      ctx.moveTo(triangleStart, size.height);
+      ctx.lineTo(triangleStart - 3, 0);
+      ctx.lineTo(triangleStart + 3, 0);
+      ctx.lineTo(triangleStart, size.height);
+      ctx.fill();
+      ctx.closePath();
+    }
+
+    ctx.restore();
   }
 }
diff --git a/ui/src/frontend/time_axis_panel.ts b/ui/src/frontend/time_axis_panel.ts
index 3f6dc64..4819d54 100644
--- a/ui/src/frontend/time_axis_panel.ts
+++ b/ui/src/frontend/time_axis_panel.ts
@@ -14,11 +14,15 @@
 
 import m from 'mithril';
 
-import {timeToString} from '../common/time';
+import {
+  tpTimeToSeconds,
+  tpTimeToString,
+} from '../common/time';
 
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {globals} from './globals';
 import {
+  getMaxMajorTicks,
   TickGenerator,
   TickType,
   timeScaleForVisibleWindow,
@@ -35,21 +39,33 @@
     ctx.font = '10px Roboto Condensed';
     ctx.textAlign = 'left';
 
-    const startTime = timeToString(globals.state.traceTime.startSec);
+    const startTime = tpTimeToString(globals.state.traceTime.start);
     ctx.fillText(startTime + ' +', 6, 11);
 
+    ctx.save();
+    ctx.beginPath();
+    ctx.rect(TRACK_SHELL_WIDTH, 0, size.width - TRACK_SHELL_WIDTH, size.height);
+    ctx.clip();
+
     // Draw time axis.
-    const timeScale = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
-    if (timeScale.timeSpan.duration > 0 && timeScale.widthPx > 0) {
-      const tickGen = new TickGenerator(timeScale);
-      for (const {type, time, position} of tickGen) {
+    const span = globals.frontendLocalState.visibleWindow.timestampSpan;
+    if (size.width > TRACK_SHELL_WIDTH && span.duration > 0n) {
+      const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH);
+      const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
+      const tickGen =
+          new TickGenerator(span, maxMajorTicks, globals.state.traceTime.start);
+      for (const {type, time} of tickGen) {
+        const position = Math.floor(map.tpTimeToPx(time));
+        const sec = tpTimeToSeconds(time - globals.state.traceTime.start);
         if (type === TickType.MAJOR) {
           ctx.fillRect(position, 0, 1, size.height);
-          ctx.fillText(time.toFixed(tickGen.digits) + ' s', position + 5, 10);
+          ctx.fillText(sec.toFixed(tickGen.digits) + ' s', position + 5, 10);
         }
       }
     }
 
+    ctx.restore();
+
     ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height);
   }
 }
diff --git a/ui/src/frontend/time_scale.ts b/ui/src/frontend/time_scale.ts
index 6f0307a..7804348 100644
--- a/ui/src/frontend/time_scale.ts
+++ b/ui/src/frontend/time_scale.ts
@@ -12,95 +12,92 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {assertFalse, assertTrue} from '../base/logging';
-import {TimeSpan} from '../common/time';
+import {assertTrue} from '../base/logging';
+import {
+  HighPrecisionTime,
+  HighPrecisionTimeSpan,
+} from '../common/high_precision_time';
+import {Span} from '../common/time';
+import {
+  TPDuration,
+  TPTime,
+} from '../common/time';
 
-const MAX_ZOOM_SPAN_SEC = 1e-8;  // 10 ns.
-
-/**
- * Defines a mapping between number and seconds for the entire application.
- * Linearly scales time values from boundsMs to pixel values in boundsPx and
- * back.
- */
 export class TimeScale {
-  private timeBounds: TimeSpan;
-  private _startPx: number;
-  private _endPx: number;
-  private secPerPx = 0;
+  private _start: HighPrecisionTime;
+  private _durationNanos: number;
+  readonly pxSpan: PxSpan;
+  private _nanosPerPx = 0;
+  private _startSec: number;
 
-  constructor(timeBounds: TimeSpan, boundsPx: [number, number]) {
-    this.timeBounds = timeBounds;
-    this._startPx = boundsPx[0];
-    this._endPx = boundsPx[1];
-    this.updateSlope();
+  constructor(start: HighPrecisionTime, durationNanos: number, pxSpan: PxSpan) {
+    // TODO(stevegolton): Ensure duration & pxSpan > 0.
+    // assertTrue(pxSpan.start < pxSpan.end, 'Px start >= end');
+    // assertTrue(durationNanos < 0, 'Duration <= 0');
+    this.pxSpan = pxSpan;
+    this._start = start;
+    this._durationNanos = durationNanos;
+    if (durationNanos <= 0 || pxSpan.delta <= 0) {
+      this._nanosPerPx = 1;
+    } else {
+      this._nanosPerPx = durationNanos / (pxSpan.delta);
+    }
+    this._startSec = this._start.seconds;
   }
 
-  private updateSlope() {
-    this.secPerPx = this.timeBounds.duration / (this._endPx - this._startPx);
+  get timeSpan(): Span<HighPrecisionTime> {
+    const end = this._start.addNanos(this._durationNanos);
+    return new HighPrecisionTimeSpan(this._start, end);
   }
 
-  deltaTimeToPx(time: number): number {
-    return Math.round(time / this.secPerPx);
+  tpTimeToPx(ts: TPTime): number {
+    // WARNING: Number(bigint) can be surprisingly slow. Avoid in hotpath.
+    const timeOffsetNanos = Number(ts - this._start.base) - this._start.offset;
+    return this.pxSpan.start + timeOffsetNanos / this._nanosPerPx;
   }
 
-  timeToPx(time: number): number {
-    return this._startPx + (time - this.timeBounds.start) / this.secPerPx;
+  secondsToPx(seconds: number): number {
+    const timeOffset = (seconds - this._startSec) * 1e9;
+    return this.pxSpan.start + timeOffset / this._nanosPerPx;
   }
 
-  pxToTime(px: number): number {
-    return this.timeBounds.start + (px - this._startPx) * this.secPerPx;
+  hpTimeToPx(time: HighPrecisionTime): number {
+    const timeOffsetNanos = time.subtract(this._start).nanos;
+    return this.pxSpan.start + timeOffsetNanos / this._nanosPerPx;
   }
 
-  deltaPxToDuration(px: number): number {
-    return px * this.secPerPx;
+  // Convert pixels to a high precision time object, which can be futher
+  // converted to other time formats.
+  pxToHpTime(px: number): HighPrecisionTime {
+    const offsetNanos = (px - this.pxSpan.start) * this._nanosPerPx;
+    return this._start.addNanos(offsetNanos);
   }
 
-  setTimeBounds(timeBounds: TimeSpan) {
-    this.timeBounds = timeBounds;
-    this.updateSlope();
+  durationToPx(dur: TPDuration): number {
+    // WARNING: Number(bigint) can be surprisingly slow. Avoid in hotpath.
+    return Number(dur) / this._nanosPerPx;
   }
 
-  setLimitsPx(pxStart: number, pxEnd: number) {
-    assertFalse(pxStart === pxEnd);
-    assertTrue(pxStart >= 0 && pxEnd >= 0);
-    this._startPx = pxStart;
-    this._endPx = pxEnd;
-    this.updateSlope();
-  }
-
-  timeInBounds(time: number): boolean {
-    return this.timeBounds.isInBounds(time);
-  }
-
-  get startPx(): number {
-    return this._startPx;
-  }
-
-  get endPx(): number {
-    return this._endPx;
-  }
-
-  get widthPx(): number {
-    return this._endPx - this._startPx;
-  }
-
-  get timeSpan(): TimeSpan {
-    return this.timeBounds;
+  pxDeltaToDuration(pxDelta: number): HighPrecisionTime {
+    const time = pxDelta * this._nanosPerPx;
+    return HighPrecisionTime.fromNanos(time);
   }
 }
 
-export function computeZoom(
-    scale: TimeScale, span: TimeSpan, zoomFactor: number, zoomPx: number):
-    TimeSpan {
-  const startPx = scale.startPx;
-  const endPx = scale.endPx;
-  const deltaPx = endPx - startPx;
-  const deltaTime = span.end - span.start;
-  const newDeltaTime = Math.max(deltaTime * zoomFactor, MAX_ZOOM_SPAN_SEC);
-  const clampedZoomPx = Math.max(startPx, Math.min(endPx, zoomPx));
-  const zoomTime = scale.pxToTime(clampedZoomPx);
-  const r = (clampedZoomPx - startPx) / deltaPx;
-  const newStartTime = zoomTime - newDeltaTime * r;
-  const newEndTime = newStartTime + newDeltaTime;
-  return new TimeSpan(newStartTime, newEndTime);
+export class PxSpan {
+  constructor(private _start: number, private _end: number) {
+    assertTrue(_start <= _end, 'PxSpan start > end');
+  }
+
+  get start(): number {
+    return this._start;
+  }
+
+  get end(): number {
+    return this._end;
+  }
+
+  get delta(): number {
+    return this._end - this._start;
+  }
 }
diff --git a/ui/src/frontend/time_scale_unittest.ts b/ui/src/frontend/time_scale_unittest.ts
index 7a1be03..f7046de 100644
--- a/ui/src/frontend/time_scale_unittest.ts
+++ b/ui/src/frontend/time_scale_unittest.ts
@@ -12,66 +12,62 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {TimeSpan} from '../common/time';
+import {HighPrecisionTime} from '../common/high_precision_time';
 
-import {computeZoom, TimeScale} from './time_scale';
+import {PxSpan, TimeScale} from './time_scale';
 
-test('time scale to work', () => {
-  const scale = new TimeScale(new TimeSpan(0, 100), [200, 1000]);
+describe('TimeScale', () => {
+  const ts =
+      new TimeScale(new HighPrecisionTime(40n), 100, new PxSpan(200, 1000));
 
-  expect(scale.timeToPx(0)).toEqual(200);
-  expect(scale.timeToPx(100)).toEqual(1000);
-  expect(scale.timeToPx(50)).toEqual(600);
+  it('converts timescales to pixels', () => {
+    expect(ts.tpTimeToPx(40n)).toEqual(200);
+    expect(ts.tpTimeToPx(140n)).toEqual(1000);
+    expect(ts.tpTimeToPx(90n)).toEqual(600);
 
-  expect(scale.pxToTime(200)).toEqual(0);
-  expect(scale.pxToTime(1000)).toEqual(100);
-  expect(scale.pxToTime(600)).toEqual(50);
+    expect(ts.tpTimeToPx(240n)).toEqual(1800);
+    expect(ts.tpTimeToPx(-60n)).toEqual(-600);
+  });
 
-  expect(scale.deltaPxToDuration(400)).toEqual(50);
+  it('converts pixels to HPTime objects', () => {
+    let result = ts.pxToHpTime(200);
+    expect(result.base).toEqual(40n);
+    expect(result.offset).toBeCloseTo(0);
 
-  expect(scale.timeInBounds(50)).toEqual(true);
-  expect(scale.timeInBounds(0)).toEqual(true);
-  expect(scale.timeInBounds(100)).toEqual(true);
-  expect(scale.timeInBounds(-1)).toEqual(false);
-  expect(scale.timeInBounds(101)).toEqual(false);
-});
+    result = ts.pxToHpTime(1000);
+    expect(result.base).toEqual(140n);
+    expect(result.offset).toBeCloseTo(0);
 
+    result = ts.pxToHpTime(600);
+    expect(result.base).toEqual(90n);
+    expect(result.offset).toBeCloseTo(0);
 
-test('time scale to be updatable', () => {
-  const scale = new TimeScale(new TimeSpan(0, 100), [100, 1000]);
+    result = ts.pxToHpTime(1800);
+    expect(result.base).toEqual(240n);
+    expect(result.offset).toBeCloseTo(0);
 
-  expect(scale.timeToPx(0)).toEqual(100);
+    result = ts.pxToHpTime(-600);
+    expect(result.base).toEqual(-60n);
+    expect(result.offset).toBeCloseTo(0);
+  });
 
-  scale.setLimitsPx(200, 1000);
-  expect(scale.timeToPx(0)).toEqual(200);
-  expect(scale.timeToPx(100)).toEqual(1000);
+  it('converts durations to pixels', () => {
+    expect(ts.durationToPx(0n)).toEqual(0);
+    expect(ts.durationToPx(1n)).toEqual(8);
+    expect(ts.durationToPx(1000n)).toEqual(8000);
+  });
 
-  scale.setTimeBounds(new TimeSpan(0, 200));
-  expect(scale.timeToPx(0)).toEqual(200);
-  expect(scale.timeToPx(100)).toEqual(600);
-  expect(scale.timeToPx(200)).toEqual(1000);
-});
+  it('converts pxDeltaToDurations to HPTime durations', () => {
+    let result = ts.pxDeltaToDuration(0);
+    expect(result.base).toEqual(0n);
+    expect(result.offset).toBeCloseTo(0);
 
-test('it zooms', () => {
-  const span = new TimeSpan(0, 20);
-  const scale = new TimeScale(span, [0, 100]);
-  const newSpan = computeZoom(scale, span, 0.5, 50);
-  expect(newSpan.start).toEqual(5);
-  expect(newSpan.end).toEqual(15);
-});
+    result = ts.pxDeltaToDuration(1);
+    expect(result.base).toEqual(0n);
+    expect(result.offset).toBeCloseTo(0.125);
 
-test('it zooms an offset scale and span', () => {
-  const span = new TimeSpan(1000, 1020);
-  const scale = new TimeScale(span, [200, 300]);
-  const newSpan = computeZoom(scale, span, 0.5, 250);
-  expect(newSpan.start).toEqual(1005);
-  expect(newSpan.end).toEqual(1015);
-});
-
-test('it clamps zoom in', () => {
-  const span = new TimeSpan(1000, 1040);
-  const scale = new TimeScale(span, [200, 300]);
-  const newSpan = computeZoom(scale, span, 0.0000000001, 225);
-  expect((newSpan.end - newSpan.start) / 2 + newSpan.start).toBeCloseTo(1010);
-  expect(newSpan.end - newSpan.start).toBeCloseTo(1e-8);
+    result = ts.pxDeltaToDuration(100);
+    expect(result.base).toEqual(12n);
+    expect(result.offset).toBeCloseTo(0.5);
+  });
 });
diff --git a/ui/src/frontend/time_selection_panel.ts b/ui/src/frontend/time_selection_panel.ts
index 20f1c53..5711e6a 100644
--- a/ui/src/frontend/time_selection_panel.ts
+++ b/ui/src/frontend/time_selection_panel.ts
@@ -13,9 +13,13 @@
 // limitations under the License.
 
 import m from 'mithril';
+import {BigintMath} from '../base/bigint_math';
 
-import {timeToString} from '../common/time';
-import {TimeSpan} from '../common/time';
+import {Span, tpTimeToString} from '../common/time';
+import {
+  TPTime,
+  TPTimeSpan,
+} from '../common/time';
 
 import {
   BACKGROUND_COLOR,
@@ -24,6 +28,7 @@
 } from './css_constants';
 import {globals} from './globals';
 import {
+  getMaxMajorTicks,
   TickGenerator,
   TickType,
   timeScaleForVisibleWindow,
@@ -48,7 +53,7 @@
   ctx.fillStyle = FOREGROUND_COLOR;
 
   const xLeft = Math.floor(target.x);
-  const xRight = Math.ceil(target.x + target.width);
+  const xRight = Math.floor(target.x + target.width);
   const yMid = Math.floor(target.height / 2 + target.y);
   const xWidth = xRight - xLeft;
 
@@ -130,11 +135,21 @@
   renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
     ctx.fillStyle = '#999';
     ctx.fillRect(TRACK_SHELL_WIDTH - 2, 0, 2, size.height);
-    const scale = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
-    if (scale.timeSpan.duration > 0 && scale.widthPx > 0) {
-      for (const {position, type} of new TickGenerator(scale)) {
+
+    ctx.save();
+    ctx.beginPath();
+    ctx.rect(TRACK_SHELL_WIDTH, 0, size.width - TRACK_SHELL_WIDTH, size.height);
+    ctx.clip();
+
+    const span = globals.frontendLocalState.visibleWindow.timestampSpan;
+    if (size.width > TRACK_SHELL_WIDTH && span.duration > 0n) {
+      const maxMajorTicks = getMaxMajorTicks(size.width - TRACK_SHELL_WIDTH);
+      const map = timeScaleForVisibleWindow(TRACK_SHELL_WIDTH, size.width);
+      for (const {type, time} of new TickGenerator(
+               span, maxMajorTicks, globals.state.traceTime.start)) {
+        const px = Math.floor(map.tpTimeToPx(time));
         if (type === TickType.MAJOR) {
-          ctx.fillRect(position, 0, 1, size.height);
+          ctx.fillRect(px, 0, 1, size.height);
         }
       }
     }
@@ -142,17 +157,17 @@
     const localArea = globals.frontendLocalState.selectedArea;
     const selection = globals.state.currentSelection;
     if (localArea !== undefined) {
-      const start = Math.min(localArea.startSec, localArea.endSec);
-      const end = Math.max(localArea.startSec, localArea.endSec);
-      this.renderSpan(ctx, size, new TimeSpan(start, end));
+      const start = BigintMath.min(localArea.start, localArea.end);
+      const end = BigintMath.max(localArea.start, localArea.end);
+      this.renderSpan(ctx, size, new TPTimeSpan(start, end));
     } else if (selection !== null && selection.kind === 'AREA') {
       const selectedArea = globals.state.areas[selection.areaId];
-      const start = Math.min(selectedArea.startSec, selectedArea.endSec);
-      const end = Math.max(selectedArea.startSec, selectedArea.endSec);
-      this.renderSpan(ctx, size, new TimeSpan(start, end));
+      const start = BigintMath.min(selectedArea.start, selectedArea.end);
+      const end = BigintMath.max(selectedArea.start, selectedArea.end);
+      this.renderSpan(ctx, size, new TPTimeSpan(start, end));
     }
 
-    if (globals.state.hoverCursorTimestamp !== -1) {
+    if (globals.state.hoverCursorTimestamp !== -1n) {
       this.renderHover(ctx, size, globals.state.hoverCursorTimestamp);
     }
 
@@ -162,27 +177,29 @@
       if (note.noteType === 'AREA' && !noteIsSelected) {
         const selectedArea = globals.state.areas[note.areaId];
         this.renderSpan(
-            ctx,
-            size,
-            new TimeSpan(selectedArea.startSec, selectedArea.endSec));
+            ctx, size, new TPTimeSpan(selectedArea.start, selectedArea.end));
       }
     }
+
+    ctx.restore();
   }
 
-  renderHover(ctx: CanvasRenderingContext2D, size: PanelSize, ts: number) {
-    const timeScale = globals.frontendLocalState.timeScale;
-    const xPos = TRACK_SHELL_WIDTH + Math.floor(timeScale.timeToPx(ts));
-    const offsetTime = timeToString(ts - globals.state.traceTime.startSec);
-    const timeFromStart = timeToString(ts);
+  renderHover(ctx: CanvasRenderingContext2D, size: PanelSize, ts: TPTime) {
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const xPos =
+        TRACK_SHELL_WIDTH + Math.floor(visibleTimeScale.tpTimeToPx(ts));
+    const offsetTime = tpTimeToString(ts - globals.state.traceTime.start);
+    const timeFromStart = tpTimeToString(ts);
     const label = `${offsetTime} (${timeFromStart})`;
     drawIBar(ctx, xPos, this.bounds(size), label);
   }
 
-  renderSpan(ctx: CanvasRenderingContext2D, size: PanelSize, span: TimeSpan) {
-    const timeScale = globals.frontendLocalState.timeScale;
-    const xLeft = timeScale.timeToPx(span.start);
-    const xRight = timeScale.timeToPx(span.end);
-    const label = timeToString(span.duration);
+  renderSpan(
+      ctx: CanvasRenderingContext2D, size: PanelSize, span: Span<TPTime>) {
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const xLeft = visibleTimeScale.tpTimeToPx(span.start);
+    const xRight = visibleTimeScale.tpTimeToPx(span.end);
+    const label = tpTimeToString(span.duration);
     drawHBar(
         ctx,
         {
diff --git a/ui/src/frontend/trace_converter.ts b/ui/src/frontend/trace_converter.ts
index ca4ced3..0f5ca69 100644
--- a/ui/src/frontend/trace_converter.ts
+++ b/ui/src/frontend/trace_converter.ts
@@ -17,6 +17,7 @@
   ConversionJobName,
   ConversionJobStatus,
 } from '../common/conversion_jobs';
+import {TPTime} from '../common/time';
 
 import {download} from './clipboard';
 import {maybeShowErrorDialog} from './error_dialog';
@@ -106,7 +107,7 @@
 }
 
 export function convertTraceToPprofAndDownload(
-    trace: Blob, pid: number, ts: number) {
+    trace: Blob, pid: number, ts: TPTime) {
   makeWorkerAndPost({
     kind: 'ConvertTraceToPprof',
     trace,
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 2d5ab81..e826317 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -130,9 +130,11 @@
   render(ctx: CanvasRenderingContext2D) {
     globals.frontendLocalState.addVisibleTrack(this.trackState.id);
     if (this.data() === undefined && !this.frontendOnly) {
-      const {visibleWindowTime, timeScale} = globals.frontendLocalState;
-      const startPx = Math.floor(timeScale.timeToPx(visibleWindowTime.start));
-      const endPx = Math.ceil(timeScale.timeToPx(visibleWindowTime.end));
+      const {visibleWindowTime, visibleTimeScale} = globals.frontendLocalState;
+      const startPx =
+          Math.floor(visibleTimeScale.hpTimeToPx(visibleWindowTime.start));
+      const endPx =
+          Math.ceil(visibleTimeScale.hpTimeToPx(visibleWindowTime.end));
       checkerboard(ctx, this.getHeight(), startPx, endPx);
     } else {
       this.renderCanvas(ctx);
@@ -175,7 +177,7 @@
     y -= 10;
 
     // Ensure the box is on screen:
-    const endPx = globals.frontendLocalState.timeScale.endPx;
+    const endPx = globals.frontendLocalState.visibleTimeScale.pxSpan.end;
     if (x + width > endPx) {
       x -= x + width - endPx;
     }
diff --git a/ui/src/frontend/track_cache.ts b/ui/src/frontend/track_cache.ts
index 049cbf4..2adbd0c 100644
--- a/ui/src/frontend/track_cache.ts
+++ b/ui/src/frontend/track_cache.ts
@@ -52,6 +52,7 @@
 // In other words the normal window is a superset of the data of the
 // non-normal window at a higher resolution. Normalization is used to
 // avoid re-fetching data on tiny zooms/moves/resizes.
+// TODO(stevegolton): Convert to bigint timestamps.
 export class CacheKey {
   readonly startNs: number;
   readonly endNs: number;
diff --git a/ui/src/frontend/track_group_panel.ts b/ui/src/frontend/track_group_panel.ts
index c9d109f..dbab7f7 100644
--- a/ui/src/frontend/track_group_panel.ts
+++ b/ui/src/frontend/track_group_panel.ts
@@ -187,18 +187,17 @@
   }
 
   highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
-    const localState = globals.frontendLocalState;
+    const {visibleTimeScale} = globals.frontendLocalState;
     const selection = globals.state.currentSelection;
     if (!selection || selection.kind !== 'AREA') return;
     const selectedArea = globals.state.areas[selection.areaId];
+    const selectedAreaDuration = selectedArea.end - selectedArea.start;
     if (selectedArea.tracks.includes(this.trackGroupId)) {
       ctx.fillStyle = 'rgba(131, 152, 230, 0.3)';
       ctx.fillRect(
-          localState.timeScale.timeToPx(selectedArea.startSec) +
-              this.shellWidth,
+          visibleTimeScale.tpTimeToPx(selectedArea.start) + this.shellWidth,
           0,
-          localState.timeScale.deltaTimeToPx(
-              selectedArea.endSec - selectedArea.startSec),
+          visibleTimeScale.durationToPx(selectedAreaDuration),
           size.height);
     }
   }
@@ -227,20 +226,20 @@
 
     this.highlightIfTrackSelected(ctx, size);
 
-    const localState = globals.frontendLocalState;
+    const {visibleTimeScale} = globals.frontendLocalState;
     // Draw vertical line when hovering on the notes panel.
-    if (globals.state.hoveredNoteTimestamp !== -1) {
+    if (globals.state.hoveredNoteTimestamp !== -1n) {
       drawVerticalLineAtTime(
           ctx,
-          localState.timeScale,
+          visibleTimeScale,
           globals.state.hoveredNoteTimestamp,
           size.height,
           `#aaa`);
     }
-    if (globals.state.hoverCursorTimestamp !== -1) {
+    if (globals.state.hoverCursorTimestamp !== -1n) {
       drawVerticalLineAtTime(
           ctx,
-          localState.timeScale,
+          visibleTimeScale,
           globals.state.hoverCursorTimestamp,
           size.height,
           `#344596`);
@@ -251,7 +250,7 @@
           globals.sliceDetails.wakeupTs !== undefined) {
         drawVerticalLineAtTime(
             ctx,
-            localState.timeScale,
+            visibleTimeScale,
             globals.sliceDetails.wakeupTs,
             size.height,
             `black`);
@@ -265,21 +264,21 @@
             'rgba(' + hex.rgb(note.color.substr(1)).toString() + ', 0.65)';
         drawVerticalLineAtTime(
             ctx,
-            localState.timeScale,
-            globals.state.areas[note.areaId].startSec,
+            visibleTimeScale,
+            globals.state.areas[note.areaId].start,
             size.height,
             transparentNoteColor,
             1);
         drawVerticalLineAtTime(
             ctx,
-            localState.timeScale,
-            globals.state.areas[note.areaId].endSec,
+            visibleTimeScale,
+            globals.state.areas[note.areaId].end,
             size.height,
             transparentNoteColor,
             1);
       } else if (note.noteType === 'DEFAULT') {
         drawVerticalLineAtTime(
-            ctx, localState.timeScale, note.timestamp, size.height, note.color);
+            ctx, visibleTimeScale, note.timestamp, size.height, note.color);
       }
     }
   }
diff --git a/ui/src/frontend/track_panel.ts b/ui/src/frontend/track_panel.ts
index 9b3c3cc..278b73d 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -31,6 +31,26 @@
   drawVerticalLineAtTime,
 } from './vertical_line_helper';
 
+function getTitleSize(title: string): string|undefined {
+  const length = title.length;
+  if (length > 55) {
+    return '9px';
+  }
+  if (length > 50) {
+    return '10px';
+  }
+  if (length > 45) {
+    return '11px';
+  }
+  if (length > 40) {
+    return '12px';
+  }
+  if (length > 35) {
+    return '13px';
+  }
+  return undefined;
+}
+
 function isPinned(id: string) {
   return globals.state.pinnedTracks.indexOf(id) !== -1;
 }
@@ -75,7 +95,6 @@
         `.track-shell[draggable=true]`,
         {
           class: `${highlightClass} ${dragClass} ${dropClass}`,
-          onmousedown: this.onmousedown.bind(this),
           ondragstart: this.ondragstart.bind(this),
           ondragend: this.ondragend.bind(this),
           ondragover: this.ondragover.bind(this),
@@ -86,6 +105,9 @@
             'h1',
             {
               title: attrs.trackState.name,
+              style: {
+                'font-size': getTitleSize(attrs.trackState.name),
+              },
             },
             attrs.trackState.name,
             ('namespace' in attrs.trackState.config) &&
@@ -122,12 +144,6 @@
               ''));
   }
 
-  onmousedown(e: MouseEvent) {
-    // Prevent that the click is intercepted by the PanAndZoomHandler and that
-    // we start panning while dragging.
-    e.stopPropagation();
-  }
-
   ondragstart(e: DragEvent) {
     const dataTransfer = e.dataTransfer;
     if (dataTransfer === null) return;
@@ -135,7 +151,6 @@
     globals.rafScheduler.scheduleFullRedraw();
     dataTransfer.setData('perfetto/track', `${this.attrs!.trackState.id}`);
     dataTransfer.setDragImage(new Image(), 0, 0);
-    e.stopImmediatePropagation();
   }
 
   ondragend() {
@@ -347,20 +362,20 @@
   }
 
   highlightIfTrackSelected(ctx: CanvasRenderingContext2D, size: PanelSize) {
-    const localState = globals.frontendLocalState;
+    const {visibleTimeScale} = globals.frontendLocalState;
     const selection = globals.state.currentSelection;
     const trackState = this.trackState;
     if (!selection || selection.kind !== 'AREA' || trackState === undefined) {
       return;
     }
     const selectedArea = globals.state.areas[selection.areaId];
+    const selectedAreaDuration = selectedArea.end - selectedArea.start;
     if (selectedArea.tracks.includes(trackState.id)) {
-      const timeScale = localState.timeScale;
       ctx.fillStyle = SELECTION_FILL_COLOR;
       ctx.fillRect(
-          timeScale.timeToPx(selectedArea.startSec) + TRACK_SHELL_WIDTH,
+          visibleTimeScale.tpTimeToPx(selectedArea.start) + TRACK_SHELL_WIDTH,
           0,
-          timeScale.deltaTimeToPx(selectedArea.endSec - selectedArea.startSec),
+          visibleTimeScale.durationToPx(selectedAreaDuration),
           size.height);
     }
   }
@@ -381,20 +396,20 @@
 
     this.highlightIfTrackSelected(ctx, size);
 
-    const localState = globals.frontendLocalState;
+    const {visibleTimeScale} = globals.frontendLocalState;
     // Draw vertical line when hovering on the notes panel.
-    if (globals.state.hoveredNoteTimestamp !== -1) {
+    if (globals.state.hoveredNoteTimestamp !== -1n) {
       drawVerticalLineAtTime(
           ctx,
-          localState.timeScale,
+          visibleTimeScale,
           globals.state.hoveredNoteTimestamp,
           size.height,
           `#aaa`);
     }
-    if (globals.state.hoverCursorTimestamp !== -1) {
+    if (globals.state.hoverCursorTimestamp !== -1n) {
       drawVerticalLineAtTime(
           ctx,
-          localState.timeScale,
+          visibleTimeScale,
           globals.state.hoverCursorTimestamp,
           size.height,
           `#344596`);
@@ -405,7 +420,7 @@
           globals.sliceDetails.wakeupTs !== undefined) {
         drawVerticalLineAtTime(
             ctx,
-            localState.timeScale,
+            visibleTimeScale,
             globals.sliceDetails.wakeupTs,
             size.height,
             `black`);
@@ -419,21 +434,21 @@
             'rgba(' + hex.rgb(note.color.substr(1)).toString() + ', 0.65)';
         drawVerticalLineAtTime(
             ctx,
-            localState.timeScale,
-            globals.state.areas[note.areaId].startSec,
+            visibleTimeScale,
+            globals.state.areas[note.areaId].start,
             size.height,
             transparentNoteColor,
             1);
         drawVerticalLineAtTime(
             ctx,
-            localState.timeScale,
-            globals.state.areas[note.areaId].endSec,
+            visibleTimeScale,
+            globals.state.areas[note.areaId].end,
             size.height,
             transparentNoteColor,
             1);
       } else if (note.noteType === 'DEFAULT') {
         drawVerticalLineAtTime(
-            ctx, localState.timeScale, note.timestamp, size.height, note.color);
+            ctx, visibleTimeScale, note.timestamp, size.height, note.color);
       }
     }
   }
diff --git a/ui/src/frontend/vertical_line_helper.ts b/ui/src/frontend/vertical_line_helper.ts
index b1e2cfc..353d166 100644
--- a/ui/src/frontend/vertical_line_helper.ts
+++ b/ui/src/frontend/vertical_line_helper.ts
@@ -12,18 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {TPTime} from '../common/time';
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {TimeScale} from './time_scale';
 
-export function drawVerticalLineAtTime(ctx: CanvasRenderingContext2D,
-                                       timeScale: TimeScale,
-                                       time: number,
-                                       height: number,
-                                       color: string,
-                                       lineWidth = 2) {
-    const xPos = TRACK_SHELL_WIDTH + Math.floor(timeScale.timeToPx(time));
-    drawVerticalLine(ctx, xPos, height, color, lineWidth);
-  }
+export function drawVerticalLineAtTime(
+    ctx: CanvasRenderingContext2D,
+    timeScale: TimeScale,
+    time: TPTime,
+    height: number,
+    color: string,
+    lineWidth = 2) {
+  const xPos = TRACK_SHELL_WIDTH + Math.floor(timeScale.tpTimeToPx(time));
+  drawVerticalLine(ctx, xPos, height, color, lineWidth);
+}
 
 function drawVerticalLine(ctx: CanvasRenderingContext2D,
                           xPos: number,
diff --git a/ui/src/frontend/viewer_page.ts b/ui/src/frontend/viewer_page.ts
index 996cae6..1a9e096 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -13,9 +13,10 @@
 // limitations under the License.
 
 import m from 'mithril';
+import {BigintMath} from '../base/bigint_math';
 
 import {Actions} from '../common/actions';
-import {TimeSpan} from '../common/time';
+import {featureFlags} from '../common/feature_flags';
 
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {DetailsPanel} from './details_panel';
@@ -27,7 +28,6 @@
 import {AnyAttrsVnode, PanelContainer} from './panel_container';
 import {TickmarkPanel} from './tickmark_panel';
 import {TimeAxisPanel} from './time_axis_panel';
-import {computeZoom} from './time_scale';
 import {TimeSelectionPanel} from './time_selection_panel';
 import {DISMISSED_PANNING_HINT_KEY} from './topbar';
 import {TrackGroupPanel} from './track_group_panel';
@@ -35,6 +35,13 @@
 
 const SIDEBAR_WIDTH = 256;
 
+const OVERVIEW_PANEL_FLAG = featureFlags.register({
+  id: 'overviewVisible',
+  name: 'Overview Panel',
+  description: 'Show the panel providing an overview of the trace',
+  defaultValue: true,
+});
+
 // Checks if the mousePos is within 3px of the start or end of the
 // current selected time range.
 function onTimeRangeBoundary(mousePos: number): 'START'|'END'|null {
@@ -45,8 +52,9 @@
     const area = globals.frontendLocalState.selectedArea ?
         globals.frontendLocalState.selectedArea :
         globals.state.areas[selection.areaId];
-    const start = globals.frontendLocalState.timeScale.timeToPx(area.startSec);
-    const end = globals.frontendLocalState.timeScale.timeToPx(area.endSec);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const start = visibleTimeScale.tpTimeToPx(area.start);
+    const end = visibleTimeScale.tpTimeToPx(area.end);
     const startDrag = mousePos - TRACK_SHELL_WIDTH;
     const startDistance = Math.abs(start - startDrag);
     const endDistance = Math.abs(end - startDrag);
@@ -111,21 +119,14 @@
       element: panZoomEl,
       contentOffsetX: SIDEBAR_WIDTH,
       onPanned: (pannedPx: number) => {
+        const {
+          visibleTimeScale,
+        } = globals.frontendLocalState;
+
         this.keepCurrentSelection = true;
-        const traceTime = globals.state.traceTime;
-        const vizTime = globals.frontendLocalState.visibleWindowTime;
-        const origDelta = vizTime.duration;
-        const tDelta = frontendLocalState.timeScale.deltaPxToDuration(pannedPx);
-        let tStart = vizTime.start + tDelta;
-        let tEnd = vizTime.end + tDelta;
-        if (tStart < traceTime.startSec) {
-          tStart = traceTime.startSec;
-          tEnd = tStart + origDelta;
-        } else if (tEnd > traceTime.endSec) {
-          tEnd = traceTime.endSec;
-          tStart = tEnd - origDelta;
-        }
-        frontendLocalState.updateVisibleTime(new TimeSpan(tStart, tEnd));
+        const tDelta = visibleTimeScale.pxDeltaToDuration(pannedPx);
+        frontendLocalState.panVisibleWindow(tDelta);
+
         // If the user has panned they no longer need the hint.
         localStorage.setItem(DISMISSED_PANNING_HINT_KEY, 'true');
         globals.rafScheduler.scheduleRedraw();
@@ -133,11 +134,10 @@
       onZoomed: (zoomedPositionPx: number, zoomRatio: number) => {
         // TODO(hjd): Avoid hardcoding TRACK_SHELL_WIDTH.
         // TODO(hjd): Improve support for zooming in overview timeline.
-        const span = frontendLocalState.visibleWindowTime;
-        const scale = frontendLocalState.timeScale;
         const zoomPx = zoomedPositionPx - TRACK_SHELL_WIDTH;
-        const newSpan = computeZoom(scale, span, 1 - zoomRatio, zoomPx);
-        frontendLocalState.updateVisibleTime(newSpan);
+        const rect = vnode.dom.getBoundingClientRect();
+        const centerPoint = zoomPx / (rect.width - TRACK_SHELL_WIDTH);
+        frontendLocalState.zoomVisibleWindow(1 - zoomRatio, centerPoint);
         globals.rafScheduler.scheduleRedraw();
       },
       editSelection: (currentPx: number) => {
@@ -151,7 +151,7 @@
           currentY: number,
           editing: boolean) => {
         const traceTime = globals.state.traceTime;
-        const scale = frontendLocalState.timeScale;
+        const {visibleTimeScale} = frontendLocalState;
         this.keepCurrentSelection = true;
         if (editing) {
           const selection = globals.state.currentSelection;
@@ -159,33 +159,40 @@
             const area = globals.frontendLocalState.selectedArea ?
                 globals.frontendLocalState.selectedArea :
                 globals.state.areas[selection.areaId];
-            let newTime = scale.pxToTime(currentX - TRACK_SHELL_WIDTH);
+            let newTime =
+                visibleTimeScale.pxToHpTime(currentX - TRACK_SHELL_WIDTH)
+                    .toTPTime();
             // Have to check again for when one boundary crosses over the other.
             const curBoundary = onTimeRangeBoundary(prevX);
             if (curBoundary == null) return;
-            const keepTime =
-                curBoundary === 'START' ? area.endSec : area.startSec;
+            const keepTime = curBoundary === 'START' ? area.end : area.start;
             // Don't drag selection outside of current screen.
             if (newTime < keepTime) {
-              newTime = Math.max(newTime, scale.pxToTime(scale.startPx));
+              newTime = BigintMath.max(
+                  newTime, visibleTimeScale.timeSpan.start.toTPTime());
             } else {
-              newTime = Math.min(newTime, scale.pxToTime(scale.endPx));
+              newTime = BigintMath.max(
+                  newTime, visibleTimeScale.timeSpan.end.toTPTime());
             }
             // When editing the time range we always use the saved tracks,
             // since these will not change.
             frontendLocalState.selectArea(
-                Math.max(Math.min(keepTime, newTime), traceTime.startSec),
-                Math.min(Math.max(keepTime, newTime), traceTime.endSec),
+                BigintMath.max(
+                    BigintMath.min(keepTime, newTime), traceTime.start),
+                BigintMath.min(
+                    BigintMath.max(keepTime, newTime), traceTime.end),
                 globals.state.areas[selection.areaId].tracks);
           }
         } else {
           let startPx = Math.min(dragStartX, currentX) - TRACK_SHELL_WIDTH;
           let endPx = Math.max(dragStartX, currentX) - TRACK_SHELL_WIDTH;
           if (startPx < 0 && endPx < 0) return;
-          startPx = Math.max(startPx, scale.startPx);
-          endPx = Math.min(endPx, scale.endPx);
+          startPx = Math.max(startPx, visibleTimeScale.pxSpan.start);
+          endPx = Math.min(endPx, visibleTimeScale.pxSpan.end);
           frontendLocalState.selectArea(
-              scale.pxToTime(startPx), scale.pxToTime(endPx));
+              visibleTimeScale.pxToHpTime(startPx).toTPTime('floor'),
+              visibleTimeScale.pxToHpTime(endPx).toTPTime('ceil'),
+          );
           frontendLocalState.areaY.start = dragStartY;
           frontendLocalState.areaY.end = currentY;
         }
@@ -252,12 +259,20 @@
       } as TrackGroupAttrs));
     }
 
+    const overviewPanel = [];
+    if (OVERVIEW_PANEL_FLAG.get()) {
+      overviewPanel.push(m(OverviewTimelinePanel, {key: 'overview'}));
+    }
+
     return m(
         '.page',
         m('.split-panel',
           m('.pan-and-zoom-content',
             {
               onclick: () => {
+                // TODO(stevegolton): Make it possible to click buttons and
+                // things on this element without deselecting the selected
+                // element!
                 // We don't want to deselect when panning/drag selecting.
                 if (this.keepCurrentSelection) {
                   this.keepCurrentSelection = false;
@@ -269,7 +284,7 @@
             m('.pinned-panel-container', m(PanelContainer, {
                 doesScroll: false,
                 panels: [
-                  m(OverviewTimelinePanel, {key: 'overview'}),
+                  ...overviewPanel,
                   m(TimeAxisPanel, {key: 'timeaxis'}),
                   m(TimeSelectionPanel, {key: 'timeselection'}),
                   m(NotesPanel, {key: 'notes'}),
diff --git a/ui/src/frontend/widgets/button.ts b/ui/src/frontend/widgets/button.ts
index 28df66a..ac86e13 100644
--- a/ui/src/frontend/widgets/button.ts
+++ b/ui/src/frontend/widgets/button.ts
@@ -15,6 +15,7 @@
 import m from 'mithril';
 import {classNames} from '../classnames';
 import {Icon} from './icon';
+import {Popup} from './popup';
 
 interface CommonAttrs {
   // Always show the button as if the "active" pseudo class were applied, which
@@ -35,6 +36,11 @@
   disabled?: boolean;
   // Optional right icon.
   rightIcon?: string;
+  // List of space separated class names forwarded to the icon.
+  className?: string;
+  // Allow clicking this button to close parent popups.
+  // Defaults to false.
+  dismissPopup?: boolean;
   // Remaining attributes forwarded to the underlying HTML <button>.
   [htmlAttrs: string]: any;
 }
@@ -63,6 +69,8 @@
       minimal = false,
       disabled = false,
       rightIcon,
+      className,
+      dismissPopup = false,
       ...htmlAttrs
     } = attrs;
 
@@ -72,6 +80,8 @@
         compact && 'pf-compact',
         minimal && 'pf-minimal',
         (icon && !label) && 'pf-icon-only',
+        dismissPopup && Popup.DISMISS_POPUP_GROUP_CLASS,
+        className,
     );
 
     return m(
diff --git a/ui/src/frontend/widgets/duration.ts b/ui/src/frontend/widgets/duration.ts
index 6f36c95..cd18e02 100644
--- a/ui/src/frontend/widgets/duration.ts
+++ b/ui/src/frontend/widgets/duration.ts
@@ -14,14 +14,14 @@
 
 import m from 'mithril';
 
-import {fromNs, timeToCode} from '../../common/time';
+import {TPDuration, tpTimeToCode} from '../../common/time';
 
 interface DurationAttrs {
-  dur: number;
+  dur: TPDuration;
 }
 
 export class Duration implements m.ClassComponent<DurationAttrs> {
   view(vnode: m.Vnode<DurationAttrs>) {
-    return timeToCode(fromNs(vnode.attrs.dur));
+    return tpTimeToCode(vnode.attrs.dur);
   }
 }
diff --git a/ui/src/frontend/widgets/form.ts b/ui/src/frontend/widgets/form.ts
new file mode 100644
index 0000000..64bf1ee
--- /dev/null
+++ b/ui/src/frontend/widgets/form.ts
@@ -0,0 +1,61 @@
+// 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 {classNames} from '../classnames';
+
+interface FormAttrs {
+  // List of space separated class names forwarded to the icon.
+  className?: string;
+  // Remaining attributes forwarded to the underlying HTML <button>.
+  [htmlAttrs: string]: any;
+}
+
+export class Form implements m.ClassComponent<FormAttrs> {
+  view({attrs, children}: m.CVnode<FormAttrs>) {
+    const {className, ...htmlAttrs} = attrs;
+
+    const classes = classNames(
+        'pf-form',
+        className,
+    );
+
+    return m(
+        'form.pf-form',
+        {
+          class: classes,
+          ...htmlAttrs,
+        },
+        children,
+    );
+  }
+}
+
+export class FormButtonBar implements m.ClassComponent<{}> {
+  view({children}: m.CVnode<{}>) {
+    return m('.pf-form-button-bar', children);
+  }
+}
+
+interface FormLabelAttrs {
+  for: string;
+  // Remaining attributes forwarded to the underlying HTML <button>.
+  [htmlAttrs: string]: any;
+}
+
+export class FormLabel implements m.ClassComponent<FormLabelAttrs> {
+  view({attrs, children}: m.CVnode<FormLabelAttrs>) {
+    return m('label.pf-form-label', attrs, children);
+  }
+}
diff --git a/ui/src/frontend/widgets/menu.ts b/ui/src/frontend/widgets/menu.ts
index 582e6fb..b521dab 100644
--- a/ui/src/frontend/widgets/menu.ts
+++ b/ui/src/frontend/widgets/menu.ts
@@ -68,6 +68,7 @@
             ...rest,
           }),
           showArrow: false,
+          createNewGroup: false,
         },
         children,
     );
@@ -86,7 +87,7 @@
 
     const classes = classNames(
         active && 'pf-active',
-        !disabled && closePopupOnClick && 'pf-close-parent-popup-on-click',
+        !disabled && closePopupOnClick && Popup.DISMISS_POPUP_GROUP_CLASS,
     );
 
     return m(
@@ -131,6 +132,14 @@
   // Whether we should show the little arrow pointing to the trigger.
   // Defaults to true.
   showArrow?: boolean;
+  // Whether this popup should form a new popup group.
+  // When nesting popups, grouping controls how popups are closed.
+  // When closing popups via the Escape key, each group is closed one by one,
+  // starting at the topmost group in the stack.
+  // When using a magic button to close groups (see DISMISS_POPUP_GROUP_CLASS),
+  // only the group in which the button lives and it's children will be closed.
+  // Defaults to true.
+  createNewGroup?: boolean;
 }
 
 // A combination of a Popup and a Menu component.
@@ -146,7 +155,6 @@
         {
           trigger,
           position: popupPosition,
-          closeOnContentClick: true,
           ...popupAttrs,
         },
         m(Menu, children));
diff --git a/ui/src/frontend/widgets/popup.ts b/ui/src/frontend/widgets/popup.ts
index ca6feb8..b258a11 100644
--- a/ui/src/frontend/widgets/popup.ts
+++ b/ui/src/frontend/widgets/popup.ts
@@ -65,14 +65,19 @@
   isOpen?: boolean;
   // Called when the popup isOpen state should be changed in controlled mode.
   onChange?: OnChangeCallback;
-  // Close the popup if clicked on.
-  // Defaults to false.
-  closeOnContentClick?: boolean;
   // Space delimited class names applied to the popup div.
   className?: string;
   // Whether to show a little arrow pointing to our trigger element.
   // Defaults to true.
   showArrow?: boolean;
+  // Whether this popup should form a new popup group.
+  // When nesting popups, grouping controls how popups are closed.
+  // When closing popups via the Escape key, each group is closed one by one,
+  // starting at the topmost group in the stack.
+  // When using a magic button to close groups (see DISMISS_POPUP_GROUP_CLASS),
+  // only the group in which the button lives and it's children will be closed.
+  // Defaults to true.
+  createNewGroup?: boolean;
 }
 
 // A popup is a portal whose position is dynamically updated so that it floats
@@ -90,6 +95,10 @@
 
   private static readonly TRIGGER_REF = 'trigger';
   private static readonly POPUP_REF = 'popup';
+  static readonly POPUP_GROUP_CLASS = 'pf-popup-group';
+
+  // Any element with this class will close its containing popup group on click
+  static readonly DISMISS_POPUP_GROUP_CLASS = 'pf-dismiss-popup-group';
 
   view({attrs, children}: m.CVnode<PopupAttrs>): m.Children {
     const {
@@ -127,6 +136,7 @@
     const {
       className,
       showArrow = true,
+      createNewGroup = true,
     } = attrs;
 
     const portalAttrs: PortalAttrs = {
@@ -168,7 +178,8 @@
         portalAttrs,
         m('.pf-popup',
           {
-            class: classNames(className),
+            class: classNames(
+                className, createNewGroup && Popup.POPUP_GROUP_CLASS),
             ref: Popup.POPUP_REF,
           },
           showArrow && m('.pf-popup-arrow[data-popper-arrow]'),
@@ -236,14 +247,29 @@
   };
 
   private handleDocKeyPress = (e: KeyboardEvent) => {
-    if (this.closeOnEscape && e.key === 'Escape') {
-      this.closePopup();
+    // Close on escape keypress if we are in the toplevel group
+    const nextGroupElement =
+        this.popupElement?.querySelector(`.${Popup.POPUP_GROUP_CLASS}`);
+    if (!nextGroupElement) {
+      if (this.closeOnEscape && e.key === 'Escape') {
+        this.closePopup();
+      }
     }
   };
 
   private handleContentClick = (e: Event) => {
+    // Close the popup if the clicked element:
+    // - Is in the same group as this class
+    // - Has the magic class
     const target = e.target as HTMLElement;
-    if (target.closest('.pf-close-parent-popup-on-click')) {
+    const childPopup =
+        this.popupElement?.querySelector(`.${Popup.POPUP_GROUP_CLASS}`);
+    if (childPopup) {
+      if (childPopup.contains(target)) {
+        return;
+      }
+    }
+    if (target.closest(`.${Popup.DISMISS_POPUP_GROUP_CLASS}`)) {
       this.closePopup();
     }
   };
diff --git a/ui/src/frontend/widgets/timestamp.ts b/ui/src/frontend/widgets/timestamp.ts
index 4552275..d8cc841 100644
--- a/ui/src/frontend/widgets/timestamp.ts
+++ b/ui/src/frontend/widgets/timestamp.ts
@@ -14,7 +14,7 @@
 
 import m from 'mithril';
 
-import {timeToCode} from '../../common/time';
+import {tpTimeToCode} from '../../common/time';
 import {toTraceTime, TPTimestamp} from '../sql_types';
 
 interface TimestampAttrs {
@@ -23,6 +23,6 @@
 
 export class Timestamp implements m.ClassComponent<TimestampAttrs> {
   view(vnode: m.Vnode<TimestampAttrs>) {
-    return timeToCode(toTraceTime(vnode.attrs.ts));
+    return tpTimeToCode(toTraceTime(vnode.attrs.ts));
   }
 }
diff --git a/ui/src/frontend/widgets/tree.ts b/ui/src/frontend/widgets/tree.ts
index 0428a48..b362391 100644
--- a/ui/src/frontend/widgets/tree.ts
+++ b/ui/src/frontend/widgets/tree.ts
@@ -48,12 +48,16 @@
 }
 
 interface TreeNodeAttrs {
-  // Content to display on the left hand column.
+  // Content to display in the left hand column.
   // If omitted, this side will be blank.
   left?: m.Child;
-  // Content to display on the right hand column.
+  // Content to display in the right hand column.
   // If omitted, this side will be left blank.
   right?: m.Child;
+  // Content to display in the right hand column when the node is collapsed.
+  // If omitted, the value of `right` shall be shown when collapsed instead.
+  // If the node has no children, this value is never shown.
+  summary?: m.Child;
   // Whether this node is collapsed or not.
   // If omitted, collapsed state 'uncontrolled' - i.e. controlled internally.
   collapsed?: boolean;
@@ -86,8 +90,13 @@
     );
   }
 
-  private renderRight({attrs: {right}}: m.CVnode<TreeNodeAttrs>) {
-    return m('.pf-tree-right', right);
+  private renderRight(vnode: m.CVnode<TreeNodeAttrs>) {
+    const {attrs: {right, summary}} = vnode;
+    if (hasChildren(vnode) && this.collapsed) {
+      return m('.pf-tree-right', summary ?? right);
+    } else {
+      return m('.pf-tree-right', right);
+    }
   }
 
   private renderChildren(vnode: m.CVnode<TreeNodeAttrs>) {
@@ -126,3 +135,14 @@
     return collapsed;
   }
 }
+
+export function dictToTree(dict: {[key: string]: m.Child}): m.Children {
+  const children: m.Child[] = [];
+  for (const key of Object.keys(dict)) {
+    children.push(m(TreeNode, {
+      left: key,
+      right: dict[key],
+    }));
+  }
+  return m(Tree, children);
+}
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index 2c2ca9b..5d550e2 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -24,6 +24,7 @@
 import {Button} from './widgets/button';
 import {Checkbox} from './widgets/checkbox';
 import {EmptyState} from './widgets/empty_state';
+import {Form, FormButtonBar, FormLabel} from './widgets/form';
 import {Icon} from './widgets/icon';
 import {Menu, MenuDivider, MenuItem, PopupMenu2} from './widgets/menu';
 import {MultiSelect, MultiSelectDiff} from './widgets/multiselect';
@@ -540,7 +541,7 @@
                     PopupMenu2,
                     {
                       trigger: m(Anchor, {
-                        text: 'SELECT * FROM raw WHERE id = 123',
+                        text: 'SELECT * FROM ftrace_event WHERE id = 123',
                         icon: 'unfold_more',
                       }),
                     },
@@ -564,7 +565,10 @@
               }),
               m(
                   TreeNode,
-                  {left: 'Args', right: 'foo: bar, baz: qux'},
+                  {
+                    left: 'Args',
+                    summary: 'foo: string, baz: string, quux: string[4]',
+                  },
                   m(TreeNode, {left: 'foo', right: 'bar'}),
                   m(TreeNode, {left: 'baz', right: 'qux'}),
                   m(
@@ -585,6 +589,45 @@
           },
           wide: true,
         }),
+        m('h2', 'Form'),
+        m(
+          WidgetShowcase, {
+            renderWidget: () => m(
+              Form,
+              m(FormLabel, {for: 'foo'}, 'Foo'),
+              m(TextInput, {id: 'foo'}),
+              m(FormLabel, {for: 'bar'}, 'Bar'),
+              m(Select, {id: 'bar'}, [
+                m('option', {value: 'foo', label: 'Foo'}),
+                m('option', {value: 'bar', label: 'Bar'}),
+                m('option', {value: 'baz', label: 'Baz'}),
+              ]),
+              m(FormButtonBar,
+                m(Button, {label: 'Submit', rightIcon: 'chevron_right'}),
+                m(Button, {label: 'Cancel', minimal: true}),
+              )),
+          }),
+        m('h2', 'Nested Popups'),
+        m(
+          WidgetShowcase, {
+            renderWidget: () => m(
+              Popup,
+              {
+                trigger: m(Button, {label: 'Open the popup'}),
+              },
+              m(PopupMenu2,
+                {
+                  trigger: m(Button, {label: 'Select an option'}),
+                },
+                m(MenuItem, {label: 'Option 1'}),
+                m(MenuItem, {label: 'Option 2'}),
+              ),
+              m(Button, {
+                label: 'Done',
+                dismissPopup: true,
+              }),
+            ),
+          }),
     );
   },
 });
diff --git a/ui/src/traceconv/index.ts b/ui/src/traceconv/index.ts
index 714666a..df3e8a0 100644
--- a/ui/src/traceconv/index.ts
+++ b/ui/src/traceconv/index.ts
@@ -18,6 +18,7 @@
   ConversionJobName,
   ConversionJobStatus,
 } from '../common/conversion_jobs';
+import {TPTime} from '../common/time';
 import traceconv from '../gen/traceconv';
 
 const selfWorker = self as {} as Worker;
@@ -176,7 +177,7 @@
   kind: 'ConvertTraceToPprof';
   trace: Blob;
   pid: number;
-  ts: number;
+  ts: TPTime;
 }
 
 function isConvertTraceToPprof(msg: Args): msg is ConvertTraceToPprofArgs {
@@ -186,8 +187,7 @@
   return true;
 }
 
-async function ConvertTraceToPprof(
-trace: Blob, pid: number, ts: number) {
+async function ConvertTraceToPprof(trace: Blob, pid: number, ts: TPTime) {
   const jobName = 'convert_pprof';
   updateJobStatus(jobName, ConversionJobStatus.InProgress);
   const args = [
diff --git a/ui/src/tracks/actual_frames/index.ts b/ui/src/tracks/actual_frames/index.ts
index 927dea4..7f5afb0 100644
--- a/ui/src/tracks/actual_frames/index.ts
+++ b/ui/src/tracks/actual_frames/index.ts
@@ -14,7 +14,7 @@
 
 import {PluginContext} from '../../common/plugin_api';
 import {NUM, NUM_NULL, STR} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {fromNs, TPDuration, TPTime, tpTimeToNanos} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {TrackController} from '../../controller/track_controller';
 import {NewTrackArgs, Track} from '../../frontend/track';
@@ -51,16 +51,17 @@
   static readonly kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
   private maxDurNs = 0;
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const startNs = toNs(start);
-    const endNs = toNs(end);
+    const startNs = tpTimeToNanos(start);
+    const endNs = tpTimeToNanos(end);
 
     const pxSize = this.pxSize();
 
     // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
     // be an even number, so we can snap in the middle.
-    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
+    const bucketNs =
+        Math.max(Math.round(Number(resolution) * pxSize / 2) * 2, 1);
 
     if (this.maxDurNs === 0) {
       const maxDurResult = await this.query(`
diff --git a/ui/src/tracks/android_log/index.ts b/ui/src/tracks/android_log/index.ts
index b2a824b..6c8a1bc 100644
--- a/ui/src/tracks/android_log/index.ts
+++ b/ui/src/tracks/android_log/index.ts
@@ -14,7 +14,7 @@
 
 import {PluginContext} from '../../common/plugin_api';
 import {NUM} from '../../common/query_result';
-import {fromNs, toNsCeil, toNsFloor} from '../../common/time';
+import {fromNs, TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {LIMIT} from '../../common/track_data';
 import {
@@ -61,21 +61,15 @@
 class AndroidLogTrackController extends TrackController<Config, Data> {
   static readonly kind = ANDROID_LOGS_TRACK_KIND;
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const startNs = toNsFloor(start);
-    const endNs = toNsCeil(end);
-
-    // |resolution| is in s/px the frontend wants.
-    const quantNs = toNsCeil(resolution);
-
     const queryRes = await this.query(`
       select
-        cast(ts / ${quantNs} as integer) * ${quantNs} as tsQuant,
+        cast(ts / ${resolution} as integer) * ${resolution} as tsQuant,
         prio,
         count(prio) as numEvents
       from android_logs
-      where ts >= ${startNs} and ts <= ${endNs}
+      where ts >= ${start} and ts <= ${end}
       group by tsQuant, prio
       order by tsQuant, prio limit ${LIMIT};`);
 
@@ -113,16 +107,16 @@
   }
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {visibleTimeScale, windowSpan} = globals.frontendLocalState;
 
     const data = this.data();
 
     if (data === undefined) return;  // Can't possibly draw anything.
 
-    const dataStartPx = timeScale.timeToPx(data.start);
-    const dataEndPx = timeScale.timeToPx(data.end);
-    const visibleStartPx = timeScale.timeToPx(visibleWindowTime.start);
-    const visibleEndPx = timeScale.timeToPx(visibleWindowTime.end);
+    const dataStartPx = visibleTimeScale.tpTimeToPx(data.start);
+    const dataEndPx = visibleTimeScale.tpTimeToPx(data.end);
+    const visibleStartPx = windowSpan.start;
+    const visibleEndPx = windowSpan.end;
 
     checkerboardExcept(
         ctx,
@@ -133,7 +127,7 @@
         dataEndPx);
 
     const quantWidth =
-        Math.max(EVT_PX, timeScale.deltaTimeToPx(data.resolution));
+        Math.max(EVT_PX, visibleTimeScale.durationToPx(data.resolution));
     const blockH = RECT_HEIGHT / LEVELS.length;
     for (let i = 0; i < data.timestamps.length; i++) {
       for (let lev = 0; lev < LEVELS.length; lev++) {
@@ -143,7 +137,7 @@
         }
         if (!hasEventsForCurColor) continue;
         ctx.fillStyle = LEVELS[lev].color;
-        const px = Math.floor(timeScale.timeToPx(data.timestamps[i]));
+        const px = Math.floor(visibleTimeScale.secondsToPx(data.timestamps[i]));
         ctx.fillRect(px, MARGIN_TOP + blockH * lev, quantWidth, blockH);
       }  // for(lev)
     }    // for (timestamps)
diff --git a/ui/src/tracks/async_slices/index.ts b/ui/src/tracks/async_slices/index.ts
index 338e02a..997577f 100644
--- a/ui/src/tracks/async_slices/index.ts
+++ b/ui/src/tracks/async_slices/index.ts
@@ -13,8 +13,8 @@
 // limitations under the License.
 
 import {PluginContext} from '../../common/plugin_api';
-import {NUM, NUM_NULL, STR} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {LONG_NULL, NUM, STR} from '../../common/query_result';
+import {fromNs, TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -43,26 +43,24 @@
 
 class AsyncSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = ASYNC_SLICE_TRACK_KIND;
-  private maxDurNs = 0;
+  private maxDurNs: TPDuration = 0n;
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const startNs = toNs(start);
-    const endNs = toNs(end);
-
     const pxSize = this.pxSize();
 
     // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
     // be an even number, so we can snap in the middle.
-    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
+    const bucketNs =
+        Math.max(Math.round(Number(resolution) * pxSize / 2) * 2, 1);
 
-    if (this.maxDurNs === 0) {
+    if (this.maxDurNs === 0n) {
       const maxDurResult = await this.query(`
         select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
         as maxDur from experimental_slice_layout
         where filter_track_ids = '${this.config.trackIds.join(',')}'
       `);
-      this.maxDurNs = maxDurResult.firstRow({maxDur: NUM_NULL}).maxDur || 0;
+      this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
     }
 
     const queryRes = await this.query(`
@@ -78,8 +76,8 @@
       from experimental_slice_layout
       where
         filter_track_ids = '${this.config.trackIds.join(',')}' and
-        ts >= ${startNs - this.maxDurNs} and
-        ts <= ${endNs}
+        ts >= ${start - this.maxDurNs} and
+        ts <= ${end}
       group by tsq, layout_depth
       order by tsq, layout_depth
     `);
diff --git a/ui/src/tracks/chrome_slices/index.ts b/ui/src/tracks/chrome_slices/index.ts
index 23f9a5e..4e70507 100644
--- a/ui/src/tracks/chrome_slices/index.ts
+++ b/ui/src/tracks/chrome_slices/index.ts
@@ -15,9 +15,16 @@
 import {Actions} from '../../common/actions';
 import {cropText, drawIncompleteSlice} from '../../common/canvas_utils';
 import {colorForThreadIdleSlice, hslForSlice} from '../../common/colorizer';
+import {
+  HighPrecisionTime,
+} from '../../common/high_precision_time';
 import {PluginContext} from '../../common/plugin_api';
-import {NUM, NUM_NULL, STR} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {LONG_NULL, NUM, NUM_NULL, STR} from '../../common/query_result';
+import {
+  fromNs,
+  TPDuration,
+  TPTime,
+} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -60,27 +67,25 @@
 
 export class ChromeSliceTrackController extends TrackController<Config, Data> {
   static kind = SLICE_TRACK_KIND;
-  private maxDurNs = 0;
+  private maxDurNs: TPDuration = 0n;
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const startNs = toNs(start);
-    const endNs = toNs(end);
-
     const pxSize = this.pxSize();
 
     // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
     // be an even number, so we can snap in the middle.
-    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
+    const bucketNs =
+        Math.max(Math.round(Number(resolution) * pxSize / 2) * 2, 1);
 
     const tableName = this.namespaceTable('slice');
 
-    if (this.maxDurNs === 0) {
+    if (this.maxDurNs === 0n) {
       const query = `
           SELECT max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
           AS maxDur FROM ${tableName} WHERE track_id = ${this.config.trackId}`;
       const queryRes = await this.query(query);
-      this.maxDurNs = queryRes.firstRow({maxDur: NUM_NULL}).maxDur || 0;
+      this.maxDurNs = queryRes.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
     }
 
     // Buckets are always even and positive, don't quantize once we zoom to
@@ -103,8 +108,8 @@
         thread_dur as threadDur
       FROM ${tableName}
       WHERE track_id = ${this.config.trackId} AND
-        ts >= (${startNs - this.maxDurNs}) AND
-        ts <= ${endNs}
+        ts >= (${start - this.maxDurNs}) AND
+        ts <= ${end}
       GROUP BY depth, tsq`;
     const queryRes = await this.query(query);
 
@@ -209,7 +214,7 @@
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
 
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {visibleTimeScale, visibleWindowTime} = globals.frontendLocalState;
     const data = this.data();
 
     if (data === undefined) return;  // Can't possibly draw anything.
@@ -219,10 +224,10 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        timeScale.timeToPx(visibleWindowTime.start),
-        timeScale.timeToPx(visibleWindowTime.end),
-        timeScale.timeToPx(data.start),
-        timeScale.timeToPx(data.end),
+        visibleTimeScale.hpTimeToPx(visibleWindowTime.start),
+        visibleTimeScale.hpTimeToPx(visibleWindowTime.end),
+        visibleTimeScale.tpTimeToPx(data.start),
+        visibleTimeScale.tpTimeToPx(data.end),
     );
 
     ctx.textAlign = 'center';
@@ -245,7 +250,7 @@
       const title = data.strings[titleId];
       const colorOverride = data.colors && data.strings[data.colors[i]];
       if (isIncomplete) {  // incomplete slice
-        tEnd = visibleWindowTime.end;
+        tEnd = visibleWindowTime.end.seconds;
       }
 
       const rect = this.getSliceRect(tStart, tEnd, depth);
@@ -369,10 +374,14 @@
   getSliceIndex({x, y}: {x: number, y: number}): number|void {
     const data = this.data();
     if (data === undefined) return;
-    const {timeScale} = globals.frontendLocalState;
+    const {
+      visibleTimeScale: timeScale,
+      visibleWindowTime,
+    } = globals.frontendLocalState;
     if (y < TRACK_PADDING) return;
-    const instantWidthTime = timeScale.deltaPxToDuration(HALF_CHEVRON_WIDTH_PX);
-    const t = timeScale.pxToTime(x);
+    const instantWidthTime = timeScale.pxDeltaToDuration(HALF_CHEVRON_WIDTH_PX);
+    const instantWidthTimeSec = instantWidthTime.seconds;
+    const t = timeScale.pxToHpTime(x).seconds;
     const depth = Math.floor((y - TRACK_PADDING) / SLICE_HEIGHT);
     for (let i = 0; i < data.starts.length; i++) {
       if (depth !== data.depths[i]) {
@@ -380,13 +389,13 @@
       }
       const tStart = data.starts[i];
       if (data.isInstant[i]) {
-        if (Math.abs(tStart - t) < instantWidthTime) {
+        if (Math.abs(tStart - t) < instantWidthTimeSec) {
           return i;
         }
       } else {
         let tEnd = data.ends[i];
         if (data.isIncomplete[i]) {
-          tEnd = globals.frontendLocalState.visibleWindowTime.end;
+          tEnd = visibleWindowTime.end.seconds;
         }
         if (tStart <= t && t <= tEnd) {
           return i;
@@ -435,17 +444,28 @@
 
   getSliceRect(tStart: number, tEnd: number, depth: number): SliceRect
       |undefined {
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
-    const pxEnd = timeScale.timeToPx(visibleWindowTime.end);
-    const left = Math.max(timeScale.timeToPx(tStart), 0);
-    const right = Math.min(timeScale.timeToPx(tEnd), pxEnd);
+    const {
+      visibleTimeScale: timeScale,
+      visibleWindowTime,
+      windowSpan,
+    } = globals.frontendLocalState;
+
+    const pxEnd = windowSpan.end;
+    const left = Math.max(timeScale.secondsToPx(tStart), 0);
+    const right = Math.min(timeScale.secondsToPx(tEnd), pxEnd);
+
+    const visible =
+        !(visibleWindowTime.start.isGreaterThan(
+              HighPrecisionTime.fromSeconds(tEnd)) ||
+          visibleWindowTime.end.isLessThan(
+              HighPrecisionTime.fromSeconds(tStart)));
+
     return {
       left,
       width: Math.max(right - left, 1),
       top: TRACK_PADDING + depth * SLICE_HEIGHT,
       height: SLICE_HEIGHT,
-      visible:
-          !(tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end),
+      visible,
     };
   }
 }
diff --git a/ui/src/tracks/counter/index.ts b/ui/src/tracks/counter/index.ts
index e495499..f22e8c7 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -19,21 +19,27 @@
 import {Actions} from '../../common/actions';
 import {
   EngineProxy,
+  LONG_NULL,
   NUM,
-  NUM_NULL,
   PluginContext,
   STR,
   TrackInfo,
 } from '../../common/plugin_api';
-import {fromNs, toNs} from '../../common/time';
+import {
+  fromNs,
+  TPDuration,
+  TPTime,
+  tpTimeFromSeconds,
+} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
 } from '../../controller/track_controller';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
-import {PopupMenuButton, PopupMenuItem} from '../../frontend/popup_menu';
 import {NewTrackArgs, Track} from '../../frontend/track';
+import {Button} from '../../frontend/widgets/button';
+import {MenuItem, PopupMenu2} from '../../frontend/widgets/menu';
 
 export const COUNTER_TRACK_KIND = 'CounterTrack';
 
@@ -61,8 +67,8 @@
   name: string;
   maximumValue?: number;
   minimumValue?: number;
-  startTs?: number;
-  endTs?: number;
+  startTs?: TPTime;
+  endTs?: TPTime;
   namespace: string;
   trackId: number;
   scale?: CounterScaleOptions;
@@ -75,18 +81,16 @@
   private minimumValueSeen = 0;
   private maximumDeltaSeen = 0;
   private minimumDeltaSeen = 0;
-  private maxDurNs = 0;
+  private maxDurNs: TPDuration = 0n;
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const startNs = toNs(start);
-    const endNs = toNs(end);
-
     const pxSize = this.pxSize();
 
     // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
     // be an even number, so we can snap in the middle.
-    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
+    const bucketNs =
+        Math.max(Math.round(Number(resolution) * pxSize / 2) * 2, 1);
 
     if (!this.setup) {
       if (this.config.namespace === undefined) {
@@ -122,7 +126,7 @@
             ) as maxDur
           from ${this.tableName('counter_view')}
       `);
-      this.maxDurNs = maxDurResult.firstRow({maxDur: NUM_NULL}).maxDur || 0;
+      this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
 
       const queryRes = await this.query(`
         select
@@ -150,7 +154,7 @@
         value_at_max_ts(ts, id) as lastId,
         value_at_max_ts(ts, value) as lastValue
       from ${this.tableName('counter_view')}
-      where ts >= ${startNs - this.maxDurNs} and ts <= ${endNs}
+      where ts >= ${start - this.maxDurNs} and ts <= ${end}
       group by tsq
       order by tsq
     `);
@@ -259,18 +263,11 @@
       {name: 'DELTA_FROM_PREVIOUS', humanName: 'Delta'},
       {name: 'RATE', humanName: 'Rate'},
     ];
-    const items: PopupMenuItem[] = [];
-    for (const scale of scales) {
-      let text;
-      if (currentScale === scale.name) {
-        text = `*${scale.humanName}*`;
-      } else {
-        text = scale.humanName;
-      }
-      items.push({
-        itemType: 'regular',
-        text,
-        callback: () => {
+    const menuItems = scales.map((scale) => {
+      return m(MenuItem, {
+        label: scale.humanName,
+        active: currentScale === scale.name,
+        onclick: () => {
           this.config.scale = scale.name;
           Actions.updateTrackConfig({
             id: this.trackState.id,
@@ -278,16 +275,23 @@
           });
         },
       });
-    }
-    return m(PopupMenuButton, {
-      icon: 'show_chart',
-      items,
     });
+
+    return m(
+        PopupMenu2,
+        {
+          trigger: m(Button, {icon: 'show_chart', minimal: true}),
+        },
+        menuItems,
+    );
   }
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {
+      visibleTimeScale: timeScale,
+      windowSpan,
+    } = globals.frontendLocalState;
     const data = this.data();
 
     // Can't possibly draw anything.
@@ -323,7 +327,7 @@
       minimumValue = data.minimumRate;
     }
 
-    const endPx = Math.floor(timeScale.timeToPx(visibleWindowTime.end));
+    const endPx = windowSpan.end;
     const zeroY = MARGIN_TOP + RECT_HEIGHT / (minimumValue < 0 ? 2 : 1);
 
     // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
@@ -368,7 +372,7 @@
     ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
 
     const calculateX = (ts: number) => {
-      return Math.floor(timeScale.timeToPx(ts));
+      return Math.floor(timeScale.secondsToPx(ts));
     };
     const calculateY = (value: number) => {
       return MARGIN_TOP + RECT_HEIGHT -
@@ -429,10 +433,10 @@
       ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
       ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
 
-      const xStart = Math.floor(timeScale.timeToPx(this.hoveredTs));
+      const xStart = Math.floor(timeScale.secondsToPx(this.hoveredTs));
       const xEnd = this.hoveredTsEnd === undefined ?
           endPx :
-          Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
+          Math.floor(timeScale.secondsToPx(this.hoveredTsEnd));
       const y = MARGIN_TOP + RECT_HEIGHT -
           Math.round(((this.hoveredValue - yMin) / yRange) * RECT_HEIGHT);
 
@@ -465,9 +469,10 @@
 
     // TODO(hjd): Refactor this into checkerboardExcept
     {
-      const endPx = timeScale.timeToPx(visibleWindowTime.end);
-      const counterEndPx =
-          Math.min(timeScale.timeToPx(this.config.endTs || Infinity), endPx);
+      let counterEndPx = Infinity;
+      if (this.config.endTs) {
+        counterEndPx = Math.min(timeScale.tpTimeToPx(this.config.endTs), endPx);
+      }
 
       // Grey out RHS.
       if (counterEndPx < endPx) {
@@ -481,18 +486,18 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        timeScale.timeToPx(visibleWindowTime.start),
-        timeScale.timeToPx(visibleWindowTime.end),
-        timeScale.timeToPx(data.start),
-        timeScale.timeToPx(data.end));
+        windowSpan.start,
+        windowSpan.end,
+        timeScale.tpTimeToPx(data.start),
+        timeScale.tpTimeToPx(data.end));
   }
 
   onMouseMove(pos: {x: number, y: number}) {
     const data = this.data();
     if (data === undefined) return;
     this.mousePos = pos;
-    const {timeScale} = globals.frontendLocalState;
-    const time = timeScale.pxToTime(pos.x);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const time = visibleTimeScale.pxToHpTime(pos.x).seconds;
 
     const values = this.config.scale === 'DELTA_FROM_PREVIOUS' ?
         data.totalDeltas :
@@ -511,8 +516,8 @@
   onMouseClick({x}: {x: number}) {
     const data = this.data();
     if (data === undefined) return false;
-    const {timeScale} = globals.frontendLocalState;
-    const time = timeScale.pxToTime(x);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const time = visibleTimeScale.pxToHpTime(x).seconds;
     const [left, right] = searchSegment(data.timestamps, time);
     if (left === -1) {
       return false;
@@ -520,8 +525,8 @@
       const counterId = data.lastIds[left];
       if (counterId === -1) return true;
       globals.makeSelection(Actions.selectCounter({
-        leftTs: toNs(data.timestamps[left]),
-        rightTs: right !== -1 ? toNs(data.timestamps[right]) : -1,
+        leftTs: tpTimeFromSeconds(data.timestamps[left]),
+        rightTs: tpTimeFromSeconds(right !== -1 ? data.timestamps[right] : -1),
         id: counterId,
         trackId: this.trackState.id,
       }));
diff --git a/ui/src/tracks/cpu_freq/index.ts b/ui/src/tracks/cpu_freq/index.ts
index 8bb2e3b..7956172 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../../base/bigint_math';
 import {searchSegment} from '../../base/binary_search';
 import {assertTrue} from '../../base/logging';
 import {hueForCpu} from '../../common/colorizer';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM, NUM_NULL, QueryResult} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {fromNs, TPDuration, TPTime, tpTimeToNanos} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -96,15 +97,17 @@
     this.cachedBucketNs = bucketNs;
   }
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
     // The resolution should always be a power of two for the logic of this
     // function to make sense.
-    const resolutionNs = toNs(resolution);
-    assertTrue(Math.log2(resolutionNs) % 1 === 0);
+    assertTrue(
+        BigintMath.popcount(resolution) === 1,
+        `${resolution} is not a power of 2`);
+    const resolutionNs = Number(resolution);
 
-    const startNs = toNs(start);
-    const endNs = toNs(end);
+    const startNs = tpTimeToNanos(start);
+    const endNs = tpTimeToNanos(end);
 
     // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
     // be an even number, so we can snap in the middle.
@@ -289,7 +292,11 @@
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {
+      visibleTimeScale,
+      visibleWindowTime,
+      windowSpan,
+    } = globals.frontendLocalState;
     const data = this.data();
 
     if (data === undefined || data.timestamps.length === 0) {
@@ -302,7 +309,7 @@
     assertTrue(data.timestamps.length === data.maxFreqKHz.length);
     assertTrue(data.timestamps.length === data.lastIdleValues.length);
 
-    const endPx = timeScale.timeToPx(visibleWindowTime.end);
+    const endPx = windowSpan.end;
     const zeroY = MARGIN_TOP + RECT_HEIGHT;
 
     // Quantize the Y axis to quarters of powers of tens (7.5K, 10K, 12.5K).
@@ -326,17 +333,18 @@
     ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 55%)`;
 
     const calculateX = (timestamp: number) => {
-      return Math.floor(timeScale.timeToPx(timestamp));
+      return Math.floor(visibleTimeScale.secondsToPx(timestamp));
     };
     const calculateY = (value: number) => {
       return zeroY - Math.round((value / yMax) * RECT_HEIGHT);
     };
 
-    const [rawStartIdx] =
-        searchSegment(data.timestamps, visibleWindowTime.start);
+    const startSec = visibleWindowTime.start.seconds;
+    const endSec = visibleWindowTime.end.seconds;
+    const [rawStartIdx] = searchSegment(data.timestamps, startSec);
     const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
 
-    const [, rawEndIdx] = searchSegment(data.timestamps, visibleWindowTime.end);
+    const [, rawEndIdx] = searchSegment(data.timestamps, endSec);
     const endIdx = rawEndIdx === -1 ? data.timestamps.length : rawEndIdx;
 
     ctx.beginPath();
@@ -383,10 +391,10 @@
       // coordinates. Instead we use floating point which prevents flickering as
       // we pan and zoom; this relies on the browser anti-aliasing pixels
       // correctly.
-      const x = timeScale.timeToPx(data.timestamps[i]);
+      const x = visibleTimeScale.secondsToPx(data.timestamps[i]);
       const xEnd = i === data.lastIdleValues.length - 1 ?
           finalX :
-          timeScale.timeToPx(data.timestamps[i + 1]);
+          visibleTimeScale.secondsToPx(data.timestamps[i + 1]);
 
       const width = xEnd - x;
       const height = calculateY(data.lastFreqKHz[i]) - zeroY;
@@ -402,10 +410,10 @@
       ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
       ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
 
-      const xStart = Math.floor(timeScale.timeToPx(this.hoveredTs));
+      const xStart = Math.floor(visibleTimeScale.secondsToPx(this.hoveredTs));
       const xEnd = this.hoveredTsEnd === undefined ?
           endPx :
-          Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
+          Math.floor(visibleTimeScale.secondsToPx(this.hoveredTsEnd));
       const y = zeroY - Math.round((this.hoveredValue / yMax) * RECT_HEIGHT);
 
       // Highlight line.
@@ -446,18 +454,18 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        timeScale.timeToPx(visibleWindowTime.start),
-        timeScale.timeToPx(visibleWindowTime.end),
-        timeScale.timeToPx(data.start),
-        timeScale.timeToPx(data.end));
+        windowSpan.start,
+        windowSpan.end,
+        visibleTimeScale.tpTimeToPx(data.start),
+        visibleTimeScale.tpTimeToPx(data.end));
   }
 
   onMouseMove(pos: {x: number, y: number}) {
     const data = this.data();
     if (data === undefined) return;
     this.mousePos = pos;
-    const {timeScale} = globals.frontendLocalState;
-    const time = timeScale.pxToTime(pos.x);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const time = visibleTimeScale.pxToHpTime(pos.x).seconds;
 
     const [left, right] = searchSegment(data.timestamps, time);
     this.hoveredTs = left === -1 ? undefined : data.timestamps[left];
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index eee7b17..028d5b1 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -18,7 +18,7 @@
 import {hslForSlice} from '../../common/colorizer';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {fromNs, TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -46,7 +46,7 @@
 
 class CpuProfileTrackController extends TrackController<Config, Data> {
   static readonly kind = CPU_PROFILE_TRACK_KIND;
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
     const query = `select
         id,
@@ -105,7 +105,7 @@
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     const {
-      timeScale,
+      visibleTimeScale: timeScale,
     } = globals.frontendLocalState;
     const data = this.data();
 
@@ -120,7 +120,7 @@
       const strokeWidth = isSelected ? 3 : 0;
       this.drawMarker(
           ctx,
-          timeScale.timeToPx(fromNs(centerX)),
+          timeScale.secondsToPx(fromNs(centerX)),
           this.centerY,
           isHovered,
           strokeWidth,
@@ -146,8 +146,8 @@
       if (clusterStartIndex !== clusterEndIndex) {
         const startX = data.tsStarts[clusterStartIndex];
         const endX = data.tsStarts[clusterEndIndex];
-        const leftPx = timeScale.timeToPx(fromNs(startX)) - this.markerWidth;
-        const rightPx = timeScale.timeToPx(fromNs(endX)) + this.markerWidth;
+        const leftPx = timeScale.secondsToPx(fromNs(startX)) - this.markerWidth;
+        const rightPx = timeScale.secondsToPx(fromNs(endX)) + this.markerWidth;
         const width = rightPx - leftPx;
         ctx.fillStyle = colorForSample(callsiteId, false);
         ctx.fillRect(leftPx, MARGIN_TOP, width, BAR_HEIGHT);
@@ -179,8 +179,10 @@
   onMouseMove({x, y}: {x: number, y: number}) {
     const data = this.data();
     if (data === undefined) return;
-    const {timeScale} = globals.frontendLocalState;
-    const time = toNs(timeScale.pxToTime(x));
+    const {
+      visibleTimeScale: timeScale,
+    } = globals.frontendLocalState;
+    const time = timeScale.pxToHpTime(x).nanos;
     const [left, right] = searchSegment(data.tsStarts, time);
     const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
     this.hoveredTs = index === -1 ? undefined : data.tsStarts[index];
@@ -193,9 +195,11 @@
   onMouseClick({x, y}: {x: number, y: number}) {
     const data = this.data();
     if (data === undefined) return false;
-    const {timeScale} = globals.frontendLocalState;
+    const {
+      visibleTimeScale: timeScale,
+    } = globals.frontendLocalState;
 
-    const time = toNs(timeScale.pxToTime(x));
+    const time = timeScale.pxToHpTime(x).nanos;
     const [left, right] = searchSegment(data.tsStarts, time);
 
     const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
@@ -217,13 +221,13 @@
       right: number): number {
     let index = -1;
     if (left !== -1) {
-      const centerX = timeScale.timeToPx(fromNs(data.tsStarts[left]));
+      const centerX = timeScale.secondsToPx(fromNs(data.tsStarts[left]));
       if (this.isInMarker(x, y, centerX)) {
         index = left;
       }
     }
     if (right !== -1) {
-      const centerX = timeScale.timeToPx(fromNs(data.tsStarts[right]));
+      const centerX = timeScale.secondsToPx(fromNs(data.tsStarts[right]));
       if (this.isInMarker(x, y, centerX)) {
         index = right;
       }
diff --git a/ui/src/tracks/cpu_slices/index.ts b/ui/src/tracks/cpu_slices/index.ts
index 0953d2f..4264069 100644
--- a/ui/src/tracks/cpu_slices/index.ts
+++ b/ui/src/tracks/cpu_slices/index.ts
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../../base/bigint_math';
 import {search, searchEq, searchSegment} from '../../base/binary_search';
 import {assertTrue} from '../../base/logging';
 import {Actions} from '../../common/actions';
@@ -23,7 +24,15 @@
 import {colorForThread} from '../../common/colorizer';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM} from '../../common/query_result';
-import {fromNs, timeToString, toNs} from '../../common/time';
+import {
+  fromNs,
+  toNs,
+  TPDuration,
+  TPTime,
+  tpTimeFromSeconds,
+  tpTimeToNanos,
+  tpTimeToString,
+} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -102,16 +111,19 @@
     this.cachedBucketNs = bucketNs;
   }
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const resolutionNs = toNs(resolution);
+    assertTrue(
+        BigintMath.popcount(resolution) === 1,
+        `${resolution} is not a power of 2`);
+    const resolutionNs = Number(resolution);
 
     // The resolution should always be a power of two for the logic of this
     // function to make sense.
     assertTrue(Math.log2(resolutionNs) % 1 === 0);
 
-    const boundStartNs = toNs(start);
-    const boundEndNs = toNs(end);
+    const boundStartNs = tpTimeToNanos(start);
+    const boundEndNs = tpTimeToNanos(end);
 
     // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
     // be an even number, so we can snap in the middle.
@@ -223,7 +235,7 @@
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {visibleTimeScale, windowSpan} = globals.frontendLocalState;
     const data = this.data();
 
     if (data === undefined) return;  // Can't possibly draw anything.
@@ -233,28 +245,35 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        timeScale.timeToPx(visibleWindowTime.start),
-        timeScale.timeToPx(visibleWindowTime.end),
-        timeScale.timeToPx(data.start),
-        timeScale.timeToPx(data.end));
+        windowSpan.start,
+        windowSpan.end,
+        visibleTimeScale.tpTimeToPx(data.start),
+        visibleTimeScale.tpTimeToPx(data.end));
 
     this.renderSlices(ctx, data);
   }
 
   renderSlices(ctx: CanvasRenderingContext2D, data: Data): void {
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {
+      visibleTimeScale,
+      visibleWindowTime,
+    } = globals.frontendLocalState;
     assertTrue(data.starts.length === data.ends.length);
     assertTrue(data.starts.length === data.utids.length);
 
+    const visWindowEndPx = visibleTimeScale.hpTimeToPx(visibleWindowTime.end);
+
     ctx.textAlign = 'center';
     ctx.font = '12px Roboto Condensed';
     const charWidth = ctx.measureText('dbpqaouk').width / 8;
 
-    const rawStartIdx =
-        data.ends.findIndex((end) => end >= visibleWindowTime.start);
+    const startSec = visibleWindowTime.start.seconds;
+    const endSec = visibleWindowTime.end.seconds;
+
+    const rawStartIdx = data.ends.findIndex((end) => end >= startSec);
     const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
 
-    const [, rawEndIdx] = searchSegment(data.starts, visibleWindowTime.end);
+    const [, rawEndIdx] = searchSegment(data.starts, endSec);
     const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx;
 
     for (let i = startIdx; i < endIdx; i++) {
@@ -266,10 +285,10 @@
       // window, else it might spill over the window and the end would not be
       // visible as a zigzag line.
       if (data.ids[i] === data.lastRowId && data.isIncomplete[i]) {
-        tEnd = visibleWindowTime.end;
+        tEnd = visibleWindowTime.end.seconds;
       }
-      const rectStart = timeScale.timeToPx(tStart);
-      const rectEnd = timeScale.timeToPx(tEnd);
+      const rectStart = visibleTimeScale.secondsToPx(tStart);
+      const rectEnd = visibleTimeScale.secondsToPx(tEnd);
       const rectWidth = Math.max(1, rectEnd - rectStart);
 
       const threadInfo = globals.threads.get(utid);
@@ -317,8 +336,7 @@
           title = `${threadInfo.threadName} [${threadInfo.tid}]`;
         }
       }
-      const right =
-          Math.min(timeScale.timeToPx(visibleWindowTime.end), rectEnd);
+      const right = Math.min(visWindowEndPx, rectEnd);
       const left = Math.max(rectStart, 0);
       const visibleWidth = Math.max(right - left, 1);
       title = cropText(title, charWidth, visibleWidth);
@@ -341,8 +359,8 @@
         const tEnd = data.ends[startIndex];
         const utid = data.utids[startIndex];
         const color = colorForThread(globals.threads.get(utid));
-        const rectStart = timeScale.timeToPx(tStart);
-        const rectEnd = timeScale.timeToPx(tEnd);
+        const rectStart = visibleTimeScale.secondsToPx(tStart);
+        const rectEnd = visibleTimeScale.secondsToPx(tEnd);
         const rectWidth = Math.max(1, rectEnd - rectStart);
 
         // Draw a rectangle around the slice that is currently selected.
@@ -353,7 +371,7 @@
         ctx.closePath();
         // Draw arrow from wakeup time of current slice.
         if (details.wakeupTs) {
-          const wakeupPos = timeScale.timeToPx(details.wakeupTs);
+          const wakeupPos = visibleTimeScale.tpTimeToPx(details.wakeupTs);
           const latencyWidth = rectStart - wakeupPos;
           drawDoubleHeadedArrow(
               ctx,
@@ -362,7 +380,8 @@
               latencyWidth,
               latencyWidth >= 20);
           // Latency time with a white semi-transparent background.
-          const displayText = timeToString(tStart - details.wakeupTs);
+          const latency = tpTimeFromSeconds(tStart) - details.wakeupTs;
+          const displayText = tpTimeToString(latency);
           const measured = ctx.measureText(displayText);
           if (latencyWidth >= measured.width + 2) {
             ctx.fillStyle = 'rgba(255,255,255,0.7)';
@@ -383,7 +402,8 @@
 
       // Draw diamond if the track being drawn is the cpu of the waker.
       if (this.config.cpu === details.wakerCpu && details.wakeupTs) {
-        const wakeupPos = Math.floor(timeScale.timeToPx(details.wakeupTs));
+        const wakeupPos =
+            Math.floor(visibleTimeScale.tpTimeToPx(details.wakeupTs));
         ctx.beginPath();
         ctx.moveTo(wakeupPos, MARGIN_TOP + RECT_HEIGHT / 2 + 8);
         ctx.fillStyle = 'black';
@@ -411,13 +431,13 @@
     const data = this.data();
     this.mousePos = pos;
     if (data === undefined) return;
-    const {timeScale} = globals.frontendLocalState;
+    const {visibleTimeScale} = globals.frontendLocalState;
     if (pos.y < MARGIN_TOP || pos.y > MARGIN_TOP + RECT_HEIGHT) {
       this.utidHoveredInThisTrack = -1;
       globals.dispatch(Actions.setHoveredUtidAndPid({utid: -1, pid: -1}));
       return;
     }
-    const t = timeScale.pxToTime(pos.x);
+    const t = visibleTimeScale.pxToHpTime(pos.x).seconds;
     let hoveredUtid = -1;
 
     for (let i = 0; i < data.starts.length; i++) {
@@ -445,8 +465,8 @@
   onMouseClick({x}: {x: number}) {
     const data = this.data();
     if (data === undefined) return false;
-    const {timeScale} = globals.frontendLocalState;
-    const time = timeScale.pxToTime(x);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const time = visibleTimeScale.pxToHpTime(x).seconds;
     const index = search(data.starts, time);
     const id = index === -1 ? undefined : data.ids[index];
     if (!id || this.utidHoveredInThisTrack === -1) return false;
diff --git a/ui/src/tracks/debug/add_debug_track_menu.ts b/ui/src/tracks/debug/add_debug_track_menu.ts
index 8b04415..d14bfd8 100644
--- a/ui/src/tracks/debug/add_debug_track_menu.ts
+++ b/ui/src/tracks/debug/add_debug_track_menu.ts
@@ -16,9 +16,10 @@
 
 import {EngineProxy} from '../../common/engine';
 import {Button} from '../../frontend/widgets/button';
+import {Form, FormButtonBar, FormLabel} from '../../frontend/widgets/form';
 import {Select} from '../../frontend/widgets/select';
 import {TextInput} from '../../frontend/widgets/text_input';
-import {Tree, TreeNode} from '../../frontend/widgets/tree';
+
 import {addDebugTrack, SliceColumns} from './slice_track';
 
 export const ARG_PREFIX = 'arg_';
@@ -67,51 +68,59 @@
               },
               column));
       }
-      return m(TreeNode, {
-        left: name,
-        right: m(
-            Select,
-            {
-              oninput: (e: Event) => {
-                if (!e.target) return;
-                this.sliceColumns[name] = (e.target as HTMLSelectElement).value;
-              },
+      return [
+        m(FormLabel,
+          {for: name,
+          },
+          name),
+        m(Select,
+          {
+            id: name,
+            oninput: (e: Event) => {
+              if (!e.target) return;
+              this.sliceColumns[name] = (e.target as HTMLSelectElement).value;
             },
-            options),
-      });
+          },
+          options),
+      ];
     };
-    return [
-      m(
-          Tree,
-          m(TreeNode, {
-            left: 'Name',
-            right: m(TextInput, {
-              onkeydown: (e: KeyboardEvent) => {
-                // Allow Esc to close popup.
-                if (e.key === 'Escape') return;
-                e.stopPropagation();
-              },
-              oninput: (e: KeyboardEvent) => {
-                if (!e.target) return;
-                this.name = (e.target as HTMLInputElement).value;
+    return m(
+        Form,
+        m(FormLabel,
+          {for: 'track_name',
+          },
+          'Name'),
+        m(TextInput, {
+          id: 'track_name',
+          onkeydown: (e: KeyboardEvent) => {
+            // Allow Esc to close popup.
+            if (e.key === 'Escape') return;
+            e.stopPropagation();
+          },
+          oninput: (e: KeyboardEvent) => {
+            if (!e.target) return;
+            this.name = (e.target as HTMLInputElement).value;
+          },
+        }),
+        renderSelect('ts'),
+        renderSelect('dur'),
+        renderSelect('name'),
+        m(
+            FormButtonBar,
+            m(Button, {
+              label: 'Show',
+              dismissPopup: true,
+              onclick: (e: Event) => {
+                e.preventDefault();
+                addDebugTrack(
+                    vnode.attrs.engine,
+                    vnode.attrs.sqlViewName,
+                    this.name,
+                    this.sliceColumns,
+                    vnode.attrs.columns);
               },
             }),
-          }),
-          renderSelect('ts'),
-          renderSelect('dur'),
-          renderSelect('name'),
-          ),
-      m(Button, {
-        label: 'Show',
-        onclick: () => {
-          addDebugTrack(
-              vnode.attrs.engine,
-              vnode.attrs.sqlViewName,
-              this.name,
-              this.sliceColumns,
-              vnode.attrs.columns);
-        },
-      }),
-    ];
+            ),
+    );
   }
 }
diff --git a/ui/src/tracks/debug/details_tab.ts b/ui/src/tracks/debug/details_tab.ts
index c168eb6..50c2319 100644
--- a/ui/src/tracks/debug/details_tab.ts
+++ b/ui/src/tracks/debug/details_tab.ts
@@ -15,19 +15,21 @@
 import m from 'mithril';
 
 import {ColumnType} from '../../common/query_result';
+import {tpDurationFromSql, tpTimeFromSql} from '../../common/time';
 import {
   BottomTab,
   bottomTabRegistry,
   NewBottomTabArgs,
 } from '../../frontend/bottom_tab';
 import {globals} from '../../frontend/globals';
-import {timestampFromSqlNanos} from '../../frontend/sql_types';
+import {asTPTimestamp} from '../../frontend/sql_types';
 import {Duration} from '../../frontend/widgets/duration';
 import {Timestamp} from '../../frontend/widgets/timestamp';
-import {Tree, TreeNode} from '../../frontend/widgets/tree';
+import {dictToTree} from '../../frontend/widgets/tree';
+
 import {ARG_PREFIX} from './add_debug_track_menu';
 
-interface DebugSliceDetalsTabConfig {
+interface DebugSliceDetailsTabConfig {
   sqlTableName: string;
   id: number;
 }
@@ -42,18 +44,8 @@
   return val.toString();
 }
 
-function dictToTree(dict: {[key: string]: m.Child}): m.Children {
-  const children: m.Child[] = [];
-  for (const key of Object.keys(dict)) {
-    children.push(m(TreeNode, {
-      left: key,
-      right: dict[key],
-    }));
-  }
-  return m(Tree, children);
-}
-
-export class DebugSliceDetailsTab extends BottomTab<DebugSliceDetalsTabConfig> {
+export class DebugSliceDetailsTab extends
+    BottomTab<DebugSliceDetailsTabConfig> {
   static readonly kind = 'org.perfetto.DebugSliceDetailsTab';
 
   data: {[key: string]: ColumnType}|undefined;
@@ -78,12 +70,11 @@
     if (this.data === undefined) {
       return m('h2', 'Loading');
     }
-    // TODO(stevegolton): These type assertions are dangerous, but no more
-    // dangerous than they used to be before this change.
     const left = dictToTree({
       'Name': this.data['name'] as string,
-      'Start time': m(Timestamp, {ts: timestampFromSqlNanos(this.data['ts'])}),
-      'Duration': m(Duration, {dur: Number(this.data['dur'])}),
+      'Start time':
+          m(Timestamp, {ts: asTPTimestamp(tpTimeFromSql(this.data['ts']))}),
+      'Duration': m(Duration, {dur: tpDurationFromSql(this.data['dur'])}),
       'Debug slice id': `${this.config.sqlTableName}[${this.config.id}]`,
     });
     const args: {[key: string]: m.Child} = {};
@@ -93,7 +84,7 @@
       }
     }
     return m(
-        'div.details-panel',
+        '.details-panel',
         m('header.overview', m('span', 'Debug Slice')),
         m('.details-table-multicolumn',
           {
diff --git a/ui/src/tracks/debug/slice_track.ts b/ui/src/tracks/debug/slice_track.ts
index a871a9f..664840d 100644
--- a/ui/src/tracks/debug/slice_track.ts
+++ b/ui/src/tracks/debug/slice_track.ts
@@ -78,8 +78,8 @@
     globals.dispatch(Actions.selectDebugSlice({
       id: args.slice.id,
       sqlTableName: this.config.sqlTableName,
-      startS: args.slice.startS,
-      durationS: args.slice.durationS,
+      start: args.slice.start,
+      duration: args.slice.duration,
       trackId: this.trackId,
     }));
   }
diff --git a/ui/src/tracks/expected_frames/index.ts b/ui/src/tracks/expected_frames/index.ts
index f2ab086..f7e5121 100644
--- a/ui/src/tracks/expected_frames/index.ts
+++ b/ui/src/tracks/expected_frames/index.ts
@@ -19,8 +19,8 @@
 import {NewTrackArgs, Track} from '../../frontend/track';
 import {ChromeSliceTrack} from '../chrome_slices';
 
-import {NUM, NUM_NULL, STR} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {LONG_NULL, NUM, STR} from '../../common/query_result';
+import {TPDuration, TPTime, fromNs} from '../../common/time';
 import {
   TrackController,
 } from '../../controller/track_controller';
@@ -46,27 +46,25 @@
 
 class ExpectedFramesSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = EXPECTED_FRAMES_SLICE_TRACK_KIND;
-  private maxDurNs = 0;
+  private maxDurNs: TPDuration = 0n;
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const startNs = toNs(start);
-    const endNs = toNs(end);
-
     const pxSize = this.pxSize();
 
     // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
     // be an even number, so we can snap in the middle.
-    const bucketNs = Math.max(Math.round(resolution * 1e9 * pxSize / 2) * 2, 1);
+    const bucketNs =
+        Math.max(Math.round(Number(resolution) * pxSize / 2) * 2, 1);
 
-    if (this.maxDurNs === 0) {
+    if (this.maxDurNs === 0n) {
       const maxDurResult = await this.query(`
         select max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
           as maxDur
         from experimental_slice_layout
         where filter_track_ids = '${this.config.trackIds.join(',')}'
       `);
-      this.maxDurNs = maxDurResult.firstRow({maxDur: NUM_NULL}).maxDur || 0;
+      this.maxDurNs = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
     }
 
     const queryRes = await this.query(`
@@ -82,8 +80,8 @@
       from experimental_slice_layout
       where
         filter_track_ids = '${this.config.trackIds.join(',')}' and
-        ts >= ${startNs - this.maxDurNs} and
-        ts <= ${endNs}
+        ts >= ${start - this.maxDurNs} and
+        ts <= ${end}
       group by tsq, layout_depth
       order by tsq, layout_depth
     `);
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index e82934d..8cd40f3 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -17,7 +17,8 @@
 import {colorForString} from '../../common/colorizer';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM, STR} from '../../common/query_result';
-import {fromNs, toNsCeil, toNsFloor} from '../../common/time';
+import {fromNs, TPDuration} from '../../common/time';
+import {TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {LIMIT} from '../../common/track_data';
 import {
@@ -29,12 +30,7 @@
 
 
 export interface Data extends TrackData {
-  // Total number of  events within [start, end], before any quantization.
-  numEvents: number;
-
-  // Below: data quantized by resolution and aggregated by event priority.
   timestamps: Float64Array;
-
   names: string[];
 }
 
@@ -51,14 +47,8 @@
 class FtraceRawTrackController extends TrackController<Config, Data> {
   static readonly kind = FTRACE_RAW_TRACK_KIND;
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const startNs = toNsFloor(start);
-    const endNs = toNsCeil(end);
-
-    // |resolution| is in s/px the frontend wants.
-    const quantNs = toNsCeil(resolution);
-
     const excludeList = Array.from(globals.state.ftraceFilter.excludedNames);
     const excludeListSql = excludeList.map((s) => `'${s}'`).join(',');
     const cpuFilter =
@@ -66,35 +56,32 @@
 
     const queryRes = await this.query(`
       select
-        cast(ts / ${quantNs} as integer) * ${quantNs} as tsQuant,
+        cast(ts / ${resolution} as integer) * ${resolution} as tsQuant,
         type,
-        count(type) as numEvents,
         name
       from ftrace_event
       where
         name not in (${excludeListSql}) and
-        ts >= ${startNs} and ts <= ${endNs} ${cpuFilter}
+        ts >= ${start} and ts <= ${end} ${cpuFilter}
       group by tsQuant
       order by tsQuant limit ${LIMIT};`);
 
     const rowCount = queryRes.numRows();
-    const result = {
+    const result: Data = {
       start,
       end,
       resolution,
       length: rowCount,
-      numEvents: 0,
       timestamps: new Float64Array(rowCount),
       names: [],
-    } as Data;
+    };
 
     const it = queryRes.iter(
-        {tsQuant: NUM, type: STR, numEvents: NUM, name: STR},
+        {tsQuant: NUM, type: STR, name: STR},
     );
     for (let row = 0; it.valid(); it.next(), row++) {
       result.timestamps[row] = fromNs(it.tsQuant);
       result.names[row] = it.name;
-      result.numEvents += it.numEvents;
     }
     return result;
   }
@@ -115,16 +102,19 @@
   }
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {
+      visibleTimeScale,
+      windowSpan,
+    } = globals.frontendLocalState;
 
     const data = this.data();
 
     if (data === undefined) return;  // Can't possibly draw anything.
 
-    const dataStartPx = timeScale.timeToPx(data.start);
-    const dataEndPx = timeScale.timeToPx(data.end);
-    const visibleStartPx = timeScale.timeToPx(visibleWindowTime.start);
-    const visibleEndPx = timeScale.timeToPx(visibleWindowTime.end);
+    const dataStartPx = visibleTimeScale.tpTimeToPx(data.start);
+    const dataEndPx = visibleTimeScale.tpTimeToPx(data.end);
+    const visibleStartPx = windowSpan.start;
+    const visibleEndPx = windowSpan.end;
 
     checkerboardExcept(
         ctx,
@@ -145,7 +135,7 @@
         ${Math.min(color.l + 10, 60)}%
       )`;
       ctx.fillStyle = hsl;
-      const xPos = Math.floor(timeScale.timeToPx(data.timestamps[i]));
+      const xPos = Math.floor(visibleTimeScale.secondsToPx(data.timestamps[i]));
 
       // Draw a diamond over the event
       ctx.save();
diff --git a/ui/src/tracks/heap_profile/index.ts b/ui/src/tracks/heap_profile/index.ts
index b3eb40a..3e656f0 100644
--- a/ui/src/tracks/heap_profile/index.ts
+++ b/ui/src/tracks/heap_profile/index.ts
@@ -18,7 +18,13 @@
 import {Actions} from '../../common/actions';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM, STR} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {
+  fromNs,
+  TPDuration,
+  TPTime,
+  tpTimeFromNanos,
+  tpTimeFromSeconds,
+} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {profileType} from '../../controller/flamegraph_controller';
 import {
@@ -42,7 +48,7 @@
 
 class HeapProfileTrackController extends TrackController<Config, Data> {
   static readonly kind = HEAP_PROFILE_TRACK_KIND;
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
     if (this.config.upid === undefined) {
       return {
@@ -111,7 +117,7 @@
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     const {
-      timeScale,
+      visibleTimeScale: timeScale,
     } = globals.frontendLocalState;
     const data = this.data();
 
@@ -122,11 +128,12 @@
       const selection = globals.state.currentSelection;
       const isHovered = this.hoveredTs === centerX;
       const isSelected = selection !== null &&
-          selection.kind === 'HEAP_PROFILE' && selection.ts === centerX;
+          selection.kind === 'HEAP_PROFILE' &&
+          selection.ts === tpTimeFromSeconds(centerX);
       const strokeWidth = isSelected ? 3 : 0;
       this.drawMarker(
           ctx,
-          timeScale.timeToPx(fromNs(centerX)),
+          timeScale.secondsToPx(fromNs(centerX)),
           this.centerY,
           isHovered,
           strokeWidth);
@@ -155,8 +162,10 @@
   onMouseMove({x, y}: {x: number, y: number}) {
     const data = this.data();
     if (data === undefined) return;
-    const {timeScale} = globals.frontendLocalState;
-    const time = toNs(timeScale.pxToTime(x));
+    const {
+      visibleTimeScale: timeScale,
+    } = globals.frontendLocalState;
+    const time = timeScale.pxToHpTime(x).nanos;
     const [left, right] = searchSegment(data.tsStarts, time);
     const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
     this.hoveredTs = index === -1 ? undefined : data.tsStarts[index];
@@ -169,15 +178,18 @@
   onMouseClick({x, y}: {x: number, y: number}) {
     const data = this.data();
     if (data === undefined) return false;
-    const {timeScale} = globals.frontendLocalState;
+    const {
+      visibleTimeScale: timeScale,
+    } = globals.frontendLocalState;
 
-    const time = toNs(timeScale.pxToTime(x));
+    const time = timeScale.pxToHpTime(x).nanos;
     const [left, right] = searchSegment(data.tsStarts, time);
 
     const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
 
     if (index !== -1) {
-      const ts = data.tsStarts[index];
+      // TODO(stevegolton): Remove conversion from number to bigint.
+      const ts = tpTimeFromNanos(data.tsStarts[index]);
       const type = data.types[index];
       globals.makeSelection(Actions.selectHeapProfile(
           {id: index, upid: this.config.upid, ts, type}));
@@ -192,13 +204,13 @@
       right: number): number {
     let index = -1;
     if (left !== -1) {
-      const centerX = timeScale.timeToPx(fromNs(data.tsStarts[left]));
+      const centerX = timeScale.secondsToPx(fromNs(data.tsStarts[left]));
       if (this.isInMarker(x, y, centerX)) {
         index = left;
       }
     }
     if (right !== -1) {
-      const centerX = timeScale.timeToPx(fromNs(data.tsStarts[right]));
+      const centerX = timeScale.secondsToPx(fromNs(data.tsStarts[right]));
       if (this.isInMarker(x, y, centerX)) {
         index = right;
       }
diff --git a/ui/src/tracks/perf_samples_profile/index.ts b/ui/src/tracks/perf_samples_profile/index.ts
index cfc73dd..693e295 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -17,7 +17,12 @@
 import {PluginContext} from '../../common/plugin_api';
 import {NUM} from '../../common/query_result';
 import {ProfileType} from '../../common/state';
-import {fromNs, toNs} from '../../common/time';
+import {
+  fromNs,
+  TPDuration,
+  TPTime,
+  tpTimeFromSeconds,
+} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -39,7 +44,7 @@
 
 class PerfSamplesProfileTrackController extends TrackController<Config, Data> {
   static readonly kind = PERF_SAMPLES_PROFILE_TRACK_KIND;
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
     if (this.config.upid === undefined) {
       return {
@@ -99,7 +104,7 @@
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     const {
-      timeScale,
+      visibleTimeScale,
     } = globals.frontendLocalState;
     const data = this.data();
 
@@ -115,7 +120,7 @@
       const strokeWidth = isSelected ? 3 : 0;
       this.drawMarker(
           ctx,
-          timeScale.timeToPx(fromNs(centerX)),
+          visibleTimeScale.secondsToPx(fromNs(centerX)),
           this.centerY,
           isHovered,
           strokeWidth);
@@ -144,10 +149,11 @@
   onMouseMove({x, y}: {x: number, y: number}) {
     const data = this.data();
     if (data === undefined) return;
-    const {timeScale} = globals.frontendLocalState;
-    const time = toNs(timeScale.pxToTime(x));
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const time = visibleTimeScale.pxToHpTime(x).nanos;
     const [left, right] = searchSegment(data.tsStartsNs, time);
-    const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
+    const index =
+        this.findTimestampIndex(left, visibleTimeScale, data, x, y, right);
     this.hoveredTs = index === -1 ? undefined : data.tsStartsNs[index];
   }
 
@@ -158,9 +164,11 @@
   onMouseClick({x, y}: {x: number, y: number}) {
     const data = this.data();
     if (data === undefined) return false;
-    const {timeScale} = globals.frontendLocalState;
+    const {
+      visibleTimeScale: timeScale,
+    } = globals.frontendLocalState;
 
-    const time = toNs(timeScale.pxToTime(x));
+    const time = timeScale.pxToHpTime(x).nanos;
     const [left, right] = searchSegment(data.tsStartsNs, time);
 
     const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
@@ -170,8 +178,8 @@
       globals.makeSelection(Actions.selectPerfSamples({
         id: index,
         upid: this.config.upid,
-        leftTs: ts,
-        rightTs: ts,
+        leftTs: tpTimeFromSeconds(ts),
+        rightTs: tpTimeFromSeconds(ts),
         type: ProfileType.PERF_SAMPLE,
       }));
       return true;
@@ -185,13 +193,13 @@
       right: number): number {
     let index = -1;
     if (left !== -1) {
-      const centerX = timeScale.timeToPx(fromNs(data.tsStartsNs[left]));
+      const centerX = timeScale.secondsToPx(fromNs(data.tsStartsNs[left]));
       if (this.isInMarker(x, y, centerX)) {
         index = left;
       }
     }
     if (right !== -1) {
-      const centerX = timeScale.timeToPx(fromNs(data.tsStartsNs[right]));
+      const centerX = timeScale.secondsToPx(fromNs(data.tsStartsNs[right]));
       if (this.isInMarker(x, y, centerX)) {
         index = right;
       }
diff --git a/ui/src/tracks/process_scheduling/index.ts b/ui/src/tracks/process_scheduling/index.ts
index 95302b6..d88bc71 100644
--- a/ui/src/tracks/process_scheduling/index.ts
+++ b/ui/src/tracks/process_scheduling/index.ts
@@ -12,13 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../../base/bigint_math';
 import {searchEq, searchRange, searchSegment} from '../../base/binary_search';
 import {assertTrue} from '../../base/logging';
 import {Actions} from '../../common/actions';
 import {colorForThread} from '../../common/colorizer';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM, QueryResult} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {
+  fromNs,
+  TPDuration,
+  TPTime,
+  tpTimeFromSeconds,
+  tpTimeToNanos,
+} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -91,22 +98,23 @@
     this.cachedBucketNs = bucketNs;
   }
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
     assertTrue(this.config.upid !== null);
 
     // The resolution should always be a power of two for the logic of this
     // function to make sense.
-    const resolutionNs = toNs(resolution);
-    assertTrue(Math.log2(resolutionNs) % 1 === 0);
+    assertTrue(
+        BigintMath.popcount(resolution) === 1,
+        `${resolution} is not a power of 2`);
 
-    const startNs = toNs(start);
-    const endNs = toNs(end);
+    const startNs = tpTimeToNanos(start);
+    const endNs = tpTimeToNanos(end);
 
     // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
     // be an even number, so we can snap in the middle.
     const bucketNs =
-        Math.max(Math.round(resolutionNs * this.pxSize() / 2) * 2, 1);
+        Math.max(Math.round(Number(resolution) * this.pxSize() / 2) * 2, 1);
 
     const queryRes = await this.queryData(startNs, endNs, bucketNs);
     const numRows = queryRes.numRows();
@@ -144,7 +152,8 @@
       slices.ends[row] = fromNs(endNsQ);
       slices.cpus[row] = it.cpu;
       slices.utids[row] = it.utid;
-      slices.end = Math.max(slices.ends[row], slices.end);
+      slices.end =
+          BigintMath.max(tpTimeFromSeconds(slices.ends[row]), slices.end);
     }
     return slices;
   }
@@ -208,7 +217,10 @@
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     // TODO: fonts and colors should come from the CSS and not hardcoded here.
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {
+      visibleTimeScale,
+      visibleWindowTime,
+    } = globals.frontendLocalState;
     const data = this.data();
 
     if (data === undefined) return;  // Can't possibly draw anything.
@@ -218,19 +230,20 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        timeScale.timeToPx(visibleWindowTime.start),
-        timeScale.timeToPx(visibleWindowTime.end),
-        timeScale.timeToPx(data.start),
-        timeScale.timeToPx(data.end));
+        visibleTimeScale.hpTimeToPx(visibleWindowTime.start),
+        visibleTimeScale.hpTimeToPx(visibleWindowTime.end),
+        visibleTimeScale.tpTimeToPx(data.start),
+        visibleTimeScale.tpTimeToPx(data.end));
 
     assertTrue(data.starts.length === data.ends.length);
     assertTrue(data.starts.length === data.utids.length);
 
-    const rawStartIdx =
-        data.ends.findIndex((end) => end >= visibleWindowTime.start);
+    const startSeconds = visibleWindowTime.start.seconds;
+    const rawStartIdx = data.ends.findIndex((end) => end >= startSeconds);
     const startIdx = rawStartIdx === -1 ? data.starts.length : rawStartIdx;
 
-    const [, rawEndIdx] = searchSegment(data.starts, visibleWindowTime.end);
+    const [, rawEndIdx] =
+        searchSegment(data.starts, visibleWindowTime.end.seconds);
     const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx;
 
     const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu);
@@ -241,8 +254,8 @@
       const utid = data.utids[i];
       const cpu = data.cpus[i];
 
-      const rectStart = timeScale.timeToPx(tStart);
-      const rectEnd = timeScale.timeToPx(tEnd);
+      const rectStart = visibleTimeScale.secondsToPx(tStart);
+      const rectEnd = visibleTimeScale.secondsToPx(tEnd);
       const rectWidth = rectEnd - rectStart;
       if (rectWidth < 0.3) continue;
 
@@ -294,8 +307,8 @@
 
     const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu);
     const cpu = Math.floor((pos.y - MARGIN_TOP) / (cpuTrackHeight + 1));
-    const {timeScale} = globals.frontendLocalState;
-    const t = timeScale.pxToTime(pos.x);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const t = visibleTimeScale.pxToHpTime(pos.x).seconds;
 
     const [i, j] = searchRange(data.starts, t, searchEq(data.cpus, cpu));
     if (i === j || i >= data.starts.length || t > data.ends[i]) {
diff --git a/ui/src/tracks/process_summary/index.ts b/ui/src/tracks/process_summary/index.ts
index d2d0ee8..8b92511 100644
--- a/ui/src/tracks/process_summary/index.ts
+++ b/ui/src/tracks/process_summary/index.ts
@@ -15,7 +15,14 @@
 import {colorForTid} from '../../common/colorizer';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {
+  fromNs,
+  TPDuration,
+  TPTime,
+  tpTimeFromNanos,
+  tpTimeToNanos,
+  tpTimeToSeconds,
+} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {LIMIT} from '../../common/track_data';
 import {
@@ -45,10 +52,10 @@
   static readonly kind = PROCESS_SUMMARY_TRACK;
   private setup = false;
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const startNs = toNs(start);
-    const endNs = toNs(end);
+    const startNs = tpTimeToNanos(start);
+    const endNs = tpTimeToNanos(end);
 
     if (this.setup === false) {
       await this.query(
@@ -85,9 +92,9 @@
       this.setup = true;
     }
 
-    // |resolution| is in s/px we want # ns for 10px window:
+    // |resolution| is in ns/px we want # ns for 10px window:
     // Max value with 1 so we don't end up with resolution 0.
-    const bucketSizeNs = Math.max(1, Math.round(resolution * 10 * 1e9));
+    const bucketSizeNs = Math.max(1, Math.round(Number(resolution) * 10));
     const windowStartNs = Math.floor(startNs / bucketSizeNs) * bucketSizeNs;
     const windowDurNs = Math.max(1, endNs - windowStartNs);
 
@@ -98,14 +105,14 @@
       where rowid = 0;`);
 
     return this.computeSummary(
-        fromNs(windowStartNs), end, resolution, bucketSizeNs);
+        tpTimeFromNanos(windowStartNs), end, resolution, bucketSizeNs);
   }
 
   private async computeSummary(
-      start: number, end: number, resolution: number,
+      start: TPTime, end: TPTime, resolution: TPDuration,
       bucketSizeNs: number): Promise<Data> {
-    const startNs = toNs(start);
-    const endNs = toNs(end);
+    const startNs = Number(start);
+    const endNs = Number(end);
     const numBuckets =
         Math.min(Math.ceil((endNs - startNs) / bucketSizeNs), LIMIT);
 
@@ -167,25 +174,28 @@
   }
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {
+      visibleTimeScale,
+      windowSpan,
+    } = globals.frontendLocalState;
     const data = this.data();
     if (data === undefined) return;  // Can't possibly draw anything.
 
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        timeScale.timeToPx(visibleWindowTime.start),
-        timeScale.timeToPx(visibleWindowTime.end),
-        timeScale.timeToPx(data.start),
-        timeScale.timeToPx(data.end));
+        windowSpan.start,
+        windowSpan.end,
+        visibleTimeScale.tpTimeToPx(data.start),
+        visibleTimeScale.tpTimeToPx(data.end));
 
     this.renderSummary(ctx, data);
   }
 
   // TODO(dproy): Dedup with CPU slices.
   renderSummary(ctx: CanvasRenderingContext2D, data: Data): void {
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
-    const startPx = Math.floor(timeScale.timeToPx(visibleWindowTime.start));
+    const {visibleTimeScale, windowSpan} = globals.frontendLocalState;
+    const startPx = windowSpan.start;
     const bottomY = TRACK_HEIGHT;
 
     let lastX = startPx;
@@ -202,9 +212,10 @@
     for (let i = 0; i < data.utilizations.length; i++) {
       // TODO(dproy): Investigate why utilization is > 1 sometimes.
       const utilization = Math.min(data.utilizations[i], 1);
-      const startTime = i * data.bucketSizeSeconds + data.start;
+      const startTime =
+          i * data.bucketSizeSeconds + tpTimeToSeconds(data.start);
 
-      lastX = Math.floor(timeScale.timeToPx(startTime));
+      lastX = Math.floor(visibleTimeScale.secondsToPx(startTime));
 
       ctx.lineTo(lastX, lastY);
       lastY = MARGIN_TOP + Math.round(SUMMARY_HEIGHT * (1 - utilization));
diff --git a/ui/src/tracks/scroll_jank/event_latency_track.ts b/ui/src/tracks/scroll_jank/event_latency_track.ts
new file mode 100644
index 0000000..d805e74
--- /dev/null
+++ b/ui/src/tracks/scroll_jank/event_latency_track.ts
@@ -0,0 +1,83 @@
+// 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 {v4 as uuidv4} from 'uuid';
+
+import {Engine} from '../../common/engine';
+import {
+  generateSqlWithInternalLayout,
+} from '../../common/internal_layout_utils';
+import {PrimaryTrackSortKey, SCROLLING_TRACK_GROUP} from '../../common/state';
+import {
+  NamedSliceTrack,
+  NamedSliceTrackTypes,
+} from '../../frontend/named_slice_track';
+import {NewTrackArgs, Track} from '../../frontend/track';
+import {DecideTracksResult} from '../chrome_scroll_jank';
+
+interface EventLatencyTrackTypes extends NamedSliceTrackTypes {}
+
+export class EventLatencyTrack extends NamedSliceTrack<EventLatencyTrackTypes> {
+  static readonly kind = 'org.chromium.ScrollJank.event_latencies';
+  createdModels = false;
+
+  static create(args: NewTrackArgs): Track {
+    return new EventLatencyTrack(args);
+  }
+
+  constructor(args: NewTrackArgs) {
+    super(args);
+  }
+
+  async initSqlTable(tableName: string) {
+    if (this.createdModels) {
+      return;
+    }
+    const sql = `CREATE VIEW ${tableName} AS ` + generateSqlWithInternalLayout({
+                  columns: ['id', 'ts', 'dur', 'track_id', 'name'],
+                  layoutParams: {ts: 'ts', dur: 'dur'},
+                  sourceTable: 'slice',
+                  whereClause: 'slice.id IN ' +
+                      '(SELECT slice_id FROM event_latency_scroll_jank_cause)',
+                });
+    await this.engine.query(sql);
+    this.createdModels = true;
+  }
+
+  // At the moment we will just display the slice details. However, on select,
+  // this behavior should be customized to show jank-related data.
+}
+
+export async function addLatenciesTrack(engine: Engine):
+    Promise<DecideTracksResult> {
+  const result: DecideTracksResult = {
+    tracksToAdd: [],
+  };
+
+  await engine.query(`
+      SELECT RUN_METRIC('chrome/event_latency_scroll_jank_cause.sql');
+    `);
+
+  result.tracksToAdd.push({
+    id: uuidv4(),
+    engineId: engine.id,
+    kind: EventLatencyTrack.kind,
+    trackSortKey: PrimaryTrackSortKey.NULL_TRACK,
+    name: 'Scroll Janks',
+    config: {},
+    trackGroup: SCROLLING_TRACK_GROUP,
+  });
+
+  return result;
+}
diff --git a/ui/src/tracks/scroll_jank/index.ts b/ui/src/tracks/scroll_jank/index.ts
new file mode 100644
index 0000000..5f6d86b
--- /dev/null
+++ b/ui/src/tracks/scroll_jank/index.ts
@@ -0,0 +1,63 @@
+// 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 {featureFlags} from '../../common/feature_flags';
+import {PluginContext} from '../../common/plugin_api';
+import {Selection} from '../../common/state';
+import {CURRENT_SELECTION_TAG} from '../../frontend/details_panel';
+import {globals} from '../../frontend/globals';
+
+import {EventLatencyTrack} from './event_latency_track';
+import {TopLevelScrollDetailsTab} from './scroll_details_tab';
+import {
+  TOP_LEVEL_SCROLL_KIND,
+  TopLevelScrollTrack,
+} from './scroll_track';
+
+export const INPUT_LATENCY_TRACK = 'InputLatency::';
+export const SCROLL_JANK_PLUGIN_ID = 'perfetto.ScrollJank';
+export const ENABLE_SCROLL_JANK_PLUGIN_V2 = featureFlags.register({
+  id: 'enableScrollJankPluginV2',
+  name: 'Enable Scroll Jank plugin V2',
+  description: 'Adds new tracks and visualizations for scroll jank.',
+  defaultValue: false,
+});
+
+function onDetailsPanelSelectionChange(newSelection?: Selection) {
+  if (newSelection === undefined ||
+      newSelection.kind !== TOP_LEVEL_SCROLL_KIND) {
+    return;
+  }
+  const bottomTabList = globals.bottomTabList;
+  if (!bottomTabList) return;
+  bottomTabList.addTab({
+    kind: TopLevelScrollDetailsTab.kind,
+    tag: CURRENT_SELECTION_TAG,
+    config: {
+      sqlTableName: newSelection.sqlTableName,
+      id: newSelection.id,
+    },
+  });
+}
+
+function activate(ctx: PluginContext) {
+  ctx.registerTrack(TopLevelScrollTrack);
+  ctx.registerTrack(EventLatencyTrack);
+  ctx.registerOnDetailsPanelSelectionChange(onDetailsPanelSelectionChange);
+}
+
+export const plugin = {
+  pluginId: SCROLL_JANK_PLUGIN_ID,
+  activate,
+};
diff --git a/ui/src/tracks/scroll_jank/scroll_details_tab.ts b/ui/src/tracks/scroll_jank/scroll_details_tab.ts
new file mode 100644
index 0000000..b13fcae
--- /dev/null
+++ b/ui/src/tracks/scroll_jank/scroll_details_tab.ts
@@ -0,0 +1,90 @@
+// 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.
+
+// Panel for the top-level scrolls. For now, just show the scroll id, but we
+// can add things like scroll event count, janks, etc. as needed.
+
+import m from 'mithril';
+
+import {ColumnType} from '../../common/query_result';
+import {tpDurationFromSql, tpTimeFromSql} from '../../common/time';
+import {
+  BottomTab,
+  bottomTabRegistry,
+  NewBottomTabArgs,
+} from '../../frontend/bottom_tab';
+import {globals} from '../../frontend/globals';
+import {asTPTimestamp} from '../../frontend/sql_types';
+import {Duration} from '../../frontend/widgets/duration';
+import {Timestamp} from '../../frontend/widgets/timestamp';
+import {dictToTree} from '../../frontend/widgets/tree';
+
+interface TopLevelScrollTabConfig {
+  sqlTableName: string;
+  id: number;
+}
+
+export class TopLevelScrollDetailsTab extends
+    BottomTab<TopLevelScrollTabConfig> {
+  static readonly kind = 'org.perfetto.TopLevelScrollDetailsTab';
+
+  data: {[key: string]: ColumnType}|undefined;
+
+  static create(args: NewBottomTabArgs): TopLevelScrollDetailsTab {
+    return new TopLevelScrollDetailsTab(args);
+  }
+
+  constructor(args: NewBottomTabArgs) {
+    super(args);
+
+    this.engine
+        .query(`select * from ${this.config.sqlTableName} where id = ${
+            this.config.id}`)
+        .then((queryResult) => {
+          this.data = queryResult.firstRow({});
+          globals.rafScheduler.scheduleFullRedraw();
+        });
+  }
+
+  viewTab() {
+    if (this.data === undefined) {
+      return m('h2', 'Loading');
+    }
+
+    const left = dictToTree({
+      'Scroll Id (gesture_scroll_id)': `${this.data['id']}`,
+      'Start time':
+          m(Timestamp, {ts: asTPTimestamp(tpTimeFromSql(this.data['ts']))}),
+      'Duration': m(Duration, {dur: tpDurationFromSql(this.data['dur'])}),
+    });
+    return m(
+        '.details-panel',
+        m('header.overview', m('span', `${this.data['name']}`)),
+        m('.details-table-multicolumn', m('.half-width-panel', left)));
+  }
+
+  getTitle(): string {
+    return `Current Chrome Scroll`;
+  }
+
+  isLoading() {
+    return this.data === undefined;
+  }
+
+  renderTabCanvas() {
+    return;
+  }
+}
+
+bottomTabRegistry.register(TopLevelScrollDetailsTab);
diff --git a/ui/src/tracks/scroll_jank/scroll_track.ts b/ui/src/tracks/scroll_jank/scroll_track.ts
new file mode 100644
index 0000000..8532a45
--- /dev/null
+++ b/ui/src/tracks/scroll_jank/scroll_track.ts
@@ -0,0 +1,116 @@
+// 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 {TPTime} from 'src/common/time';
+import {v4 as uuidv4} from 'uuid';
+
+import {Actions} from '../../common/actions';
+import {Engine} from '../../common/engine';
+import {
+  generateSqlWithInternalLayout,
+} from '../../common/internal_layout_utils';
+import {
+  PrimaryTrackSortKey,
+  SCROLLING_TRACK_GROUP,
+  Selection,
+} from '../../common/state';
+import {OnSliceClickArgs} from '../../frontend/base_slice_track';
+import {globals} from '../../frontend/globals';
+import {
+  NamedSliceTrack,
+  NamedSliceTrackTypes,
+} from '../../frontend/named_slice_track';
+import {NewTrackArgs, Track} from '../../frontend/track';
+import {DecideTracksResult} from '../chrome_scroll_jank';
+
+export const TOP_LEVEL_SCROLL_KIND = 'TOP_LEVEL_SCROLL';
+
+export interface TopLevelScrollSelection {
+  kind: 'TOP_LEVEL_SCROLL';
+  id: number;
+  sqlTableName: string;
+  start: TPTime;
+  duration: TPTime;
+}
+
+export {Data} from '../chrome_slices';
+
+interface TopLevelScrollTrackTypes extends NamedSliceTrackTypes {}
+
+export class TopLevelScrollTrack extends
+    NamedSliceTrack<TopLevelScrollTrackTypes> {
+  static readonly kind = 'org.chromium.TopLevelScrolls.scrolls';
+  createdModels = false;
+
+  static create(args: NewTrackArgs): Track {
+    return new TopLevelScrollTrack(args);
+  }
+
+  constructor(args: NewTrackArgs) {
+    super(args);
+  }
+
+  async initSqlTable(tableName: string) {
+    if (this.createdModels) {
+      return;
+    }
+    const sql =
+        `CREATE VIEW ${tableName} AS ` + generateSqlWithInternalLayout({
+          columns: [`printf("Scroll %s", CAST(id AS STRING)) AS name`, '*'],
+          layoutParams: {ts: 'ts', dur: 'dur'},
+          sourceTable: 'chrome_scrolls',
+          orderByClause: 'ts',
+        });
+    await this.engine.query(sql);
+    this.createdModels = true;
+  }
+
+  isSelectionHandled(selection: Selection) {
+    if (selection.kind !== 'TOP_LEVEL_SCROLL') {
+      return false;
+    }
+    return selection.trackId === this.trackId;
+  }
+
+  onSliceClick(args: OnSliceClickArgs<TopLevelScrollTrackTypes['slice']>) {
+    globals.dispatch(Actions.selectTopLevelScrollSlice({
+      id: args.slice.id,
+      sqlTableName: this.tableName,
+      start: args.slice.start,
+      duration: args.slice.duration,
+      trackId: this.trackId,
+    }));
+  }
+}
+
+export async function addTopLevelScrollTrack(engine: Engine):
+    Promise<DecideTracksResult> {
+  const result: DecideTracksResult = {
+    tracksToAdd: [],
+  };
+
+  await engine.query(`SELECT IMPORT('chrome.chrome_scrolls');`);
+
+  result.tracksToAdd.push({
+    id: uuidv4(),
+    engineId: engine.id,
+    kind: TopLevelScrollTrack.kind,
+    trackSortKey: PrimaryTrackSortKey.ASYNC_SLICE_TRACK,
+    name: 'Top Level Scrolls',
+    config: {},
+    trackGroup: SCROLLING_TRACK_GROUP,
+  });
+
+  return result;
+}
diff --git a/ui/src/tracks/thread_state/index.ts b/ui/src/tracks/thread_state/index.ts
index 3713999..4ad1b4c 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -17,10 +17,18 @@
 import {Actions} from '../../common/actions';
 import {cropText} from '../../common/canvas_utils';
 import {colorForState} from '../../common/colorizer';
+import {
+  HighPrecisionTime,
+  HighPrecisionTimeSpan,
+} from '../../common/high_precision_time';
 import {PluginContext} from '../../common/plugin_api';
-import {NUM, NUM_NULL, STR_NULL} from '../../common/query_result';
+import {LONG, NUM, NUM_NULL, STR_NULL} from '../../common/query_result';
 import {translateState} from '../../common/thread_state';
-import {fromNs, toNs} from '../../common/time';
+import {
+  fromNs,
+  TPDuration,
+  TPTime,
+} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {TrackController} from '../../controller/track_controller';
 import {checkerboardExcept} from '../../frontend/checkerboard';
@@ -46,7 +54,7 @@
 class ThreadStateTrackController extends TrackController<Config, Data> {
   static readonly kind = THREAD_STATE_TRACK_KIND;
 
-  private maxDurNs = 0;
+  private maxDurNs: TPDuration = 0n;
 
   async onSetup() {
     await this.query(`
@@ -66,19 +74,15 @@
       select ifnull(max(dur), 0) as maxDur
       from ${this.tableName('thread_state')}
     `);
-    this.maxDurNs = queryRes.firstRow({maxDur: NUM}).maxDur;
+    this.maxDurNs = queryRes.firstRow({maxDur: LONG}).maxDur;
   }
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const resolutionNs = toNs(resolution);
-    const startNs = toNs(start);
-    const endNs = toNs(end);
-
     // ns per quantization bucket (i.e. ns per pixel). /2 * 2 is to force it to
     // be an even number, so we can snap in the middle.
     const bucketNs =
-        Math.max(Math.round(resolutionNs * this.pxSize() / 2) * 2, 1);
+        Math.max(Math.round(Number(resolution) * this.pxSize() / 2) * 2, 1);
 
     const query = `
       select
@@ -92,8 +96,8 @@
         ifnull(id, -1) as id
       from ${this.tableName('thread_state')}
       where
-        ts >= ${startNs - this.maxDurNs} and
-        ts <= ${endNs}
+        ts >= ${start - this.maxDurNs} and
+        ts <= ${end}
       group by tsq, is_sleep
       order by tsq
     `;
@@ -186,7 +190,11 @@
   }
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
-    const {timeScale, visibleWindowTime} = globals.frontendLocalState;
+    const {
+      visibleTimeScale: timeScale,
+      visibleWindowTime,
+      windowSpan,
+    } = globals.frontendLocalState;
     const data = this.data();
     const charWidth = ctx.measureText('dbpqaouk').width / 8;
 
@@ -199,10 +207,10 @@
     checkerboardExcept(
         ctx,
         this.getHeight(),
-        timeScale.timeToPx(visibleWindowTime.start),
-        timeScale.timeToPx(visibleWindowTime.end),
-        timeScale.timeToPx(data.start),
-        timeScale.timeToPx(data.end),
+        windowSpan.start,
+        windowSpan.end,
+        timeScale.tpTimeToPx(data.start),
+        timeScale.tpTimeToPx(data.end),
     );
 
     ctx.textAlign = 'center';
@@ -220,14 +228,19 @@
       const tStart = data.starts[i];
       const tEnd = data.ends[i];
       const state = data.strings[data.state[i]];
-      if (tEnd <= visibleWindowTime.start || tStart >= visibleWindowTime.end) {
+      const timeSpan = new HighPrecisionTimeSpan(
+          HighPrecisionTime.fromSeconds(tStart),
+          HighPrecisionTime.fromSeconds(tEnd),
+      );
+
+      if (!visibleWindowTime.intersects(timeSpan)) {
         continue;
       }
 
       // Don't display a slice for Task Dead.
       if (state === 'x') continue;
-      const rectStart = timeScale.timeToPx(tStart);
-      const rectEnd = timeScale.timeToPx(tEnd);
+      const rectStart = timeScale.secondsToPx(tStart);
+      const rectEnd = timeScale.secondsToPx(tEnd);
       const rectWidth = rectEnd - rectStart;
 
       const currentSelection = globals.state.currentSelection;
@@ -255,10 +268,9 @@
       if (isSelected) {
         drawRectOnSelected = () => {
           const rectStart =
-              Math.max(0 - EXCESS_WIDTH, timeScale.timeToPx(tStart));
+              Math.max(0 - EXCESS_WIDTH, timeScale.secondsToPx(tStart));
           const rectEnd = Math.min(
-              timeScale.timeToPx(visibleWindowTime.end) + EXCESS_WIDTH,
-              timeScale.timeToPx(tEnd));
+              windowSpan.end + EXCESS_WIDTH, timeScale.secondsToPx(tEnd));
           const color = colorForState(state);
           ctx.strokeStyle = `hsl(${color.h},${color.s}%,${color.l * 0.7}%)`;
           ctx.beginPath();
@@ -278,9 +290,9 @@
   onMouseClick({x}: {x: number}) {
     const data = this.data();
     if (data === undefined) return false;
-    const {timeScale} = globals.frontendLocalState;
-    const time = timeScale.pxToTime(x);
-    const index = search(data.starts, time);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const time = visibleTimeScale.pxToHpTime(x);
+    const index = search(data.starts, time.seconds);
     if (index === -1) return false;
 
     const id = data.ids[index];