Merge remote-tracking branch 'origin/master' into ui-canary

Commands:
$ git fetch origin
$ git checkout -B ui-canary -t origin/ui-canary
$ git merge --strategy=ours origin/master
$ git diff --binary origin/master | git apply --reverse --index
$ git commit --amend

End state:
$ git diff ui-canary origin/master
[no output]
$ git rev-list --count ui-canary..origin/master
0

Change-Id: I94521f6c83096b4a55996ffd7749539da856a811
diff --git a/Android.bp b/Android.bp
index e3a94e7..4265016 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",
             ],
         },
     },
@@ -679,6 +689,7 @@
         "src/android_internal/health_hal.cc",
         "src/android_internal/incident_service.cc",
         "src/android_internal/power_stats.cc",
+        "src/android_internal/statsd.cc",
         "src/android_internal/statsd_logging.cc",
         "src/android_internal/tracing_service_proxy.cc",
     ],
@@ -696,6 +707,7 @@
         "libincident",
         "liblog",
         "libservices",
+        "libstatspull",
         "libstatssocket",
         "libtracingproxy",
         "libutils",
@@ -2023,6 +2035,8 @@
         ":perfetto_src_shared_lib_test_utils",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_db",
+        ":perfetto_src_trace_processor_db_overlays_overlays",
+        ":perfetto_src_trace_processor_db_storage_storage",
         ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
         ":perfetto_src_trace_processor_importers_common_common",
@@ -2052,11 +2066,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",
@@ -8409,6 +8425,7 @@
         "src/base/scoped_file_unittest.cc",
         "src/base/small_vector_unittest.cc",
         "src/base/status_or_unittest.cc",
+        "src/base/status_unittest.cc",
         "src/base/string_splitter_unittest.cc",
         "src/base/string_utils_unittest.cc",
         "src/base/string_view_unittest.cc",
@@ -9384,20 +9401,54 @@
     srcs: [
         "src/trace_processor/db/column.cc",
         "src/trace_processor/db/column_storage.cc",
-        "src/trace_processor/db/numeric_storage.cc",
-        "src/trace_processor/db/storage.cc",
+        "src/trace_processor/db/null_overlay.cc",
         "src/trace_processor/db/table.cc",
         "src/trace_processor/db/view.cc",
     ],
 }
 
+// GN: //src/trace_processor/db/overlays:overlays
+filegroup {
+    name: "perfetto_src_trace_processor_db_overlays_overlays",
+    srcs: [
+        "src/trace_processor/db/overlays/null_overlay.cc",
+        "src/trace_processor/db/overlays/selector_overlay.cc",
+        "src/trace_processor/db/overlays/storage_overlay.cc",
+    ],
+}
+
+// GN: //src/trace_processor/db/overlays:unittests
+filegroup {
+    name: "perfetto_src_trace_processor_db_overlays_unittests",
+    srcs: [
+        "src/trace_processor/db/overlays/null_overlay_unittest.cc",
+        "src/trace_processor/db/overlays/selector_overlay_unittest.cc",
+    ],
+}
+
+// GN: //src/trace_processor/db/storage:storage
+filegroup {
+    name: "perfetto_src_trace_processor_db_storage_storage",
+    srcs: [
+        "src/trace_processor/db/storage/numeric_storage.cc",
+        "src/trace_processor/db/storage/storage.cc",
+    ],
+}
+
+// GN: //src/trace_processor/db/storage:unittests
+filegroup {
+    name: "perfetto_src_trace_processor_db_storage_unittests",
+    srcs: [
+        "src/trace_processor/db/storage/storage_unittest.cc",
+    ],
+}
+
 // GN: //src/trace_processor/db:unittests
 filegroup {
     name: "perfetto_src_trace_processor_db_unittests",
     srcs: [
         "src/trace_processor/db/column_storage_overlay_unittest.cc",
         "src/trace_processor/db/compare_unittest.cc",
-        "src/trace_processor/db/storage_unittest.cc",
         "src/trace_processor/db/view_unittest.cc",
     ],
 }
@@ -10023,6 +10074,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",
@@ -10151,14 +10203,22 @@
         "src/trace_processor/prelude/functions/create_view_function.cc",
         "src/trace_processor/prelude/functions/import.cc",
         "src/trace_processor/prelude/functions/layout_functions.cc",
+        "src/trace_processor/prelude/functions/math.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",
+    ],
+}
+
 // GN: //src/trace_processor/prelude/functions:unittests
 filegroup {
     name: "perfetto_src_trace_processor_prelude_functions_unittests",
@@ -10184,6 +10244,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",
@@ -10198,7 +10266,6 @@
         "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",
     ],
 }
@@ -10232,6 +10299,7 @@
         "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/sched_tables.py",
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
@@ -10312,33 +10380,38 @@
     ],
 }
 
+// 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/perfetto_sql_engine.cc",
+        "src/trace_processor/sqlite/perfetto_sql_parser.cc",
         "src/trace_processor/sqlite/sql_stats_table.cc",
         "src/trace_processor/sqlite/sqlite_engine.cc",
+        "src/trace_processor/sqlite/sqlite_table.cc",
+        "src/trace_processor/sqlite/sqlite_tokenizer.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",
     srcs: [
         "src/trace_processor/sqlite/db_sqlite_table_unittest.cc",
+        "src/trace_processor/sqlite/perfetto_sql_parser_unittest.cc",
         "src/trace_processor/sqlite/query_constraints_unittest.cc",
+        "src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc",
         "src/trace_processor/sqlite/sqlite_utils_unittest.cc",
     ],
 }
@@ -10348,15 +10421,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/chrome/histograms.sql",
+        "src/trace_processor/stdlib/chrome/speedometer.sql",
         "src/trace_processor/stdlib/common/counters.sql",
         "src/trace_processor/stdlib/common/cpus.sql",
         "src/trace_processor/stdlib/common/metadata.sql",
@@ -10443,6 +10522,7 @@
         "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/sched_tables.py",
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
@@ -10458,6 +10538,7 @@
         "src/trace_processor/tables/memory_tables_py.h",
         "src/trace_processor/tables/metadata_tables_py.h",
         "src/trace_processor/tables/profiler_tables_py.h",
+        "src/trace_processor/tables/sched_tables_py.h",
         "src/trace_processor/tables/slice_tables_py.h",
         "src/trace_processor/tables/trace_proto_tables_py.h",
         "src/trace_processor/tables/track_tables_py.h",
@@ -10477,6 +10558,7 @@
         "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/sched_tables.py",
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
@@ -11299,6 +11381,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",
     ],
 }
 
@@ -11988,6 +12079,10 @@
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_containers_unittests",
         ":perfetto_src_trace_processor_db_db",
+        ":perfetto_src_trace_processor_db_overlays_overlays",
+        ":perfetto_src_trace_processor_db_overlays_unittests",
+        ":perfetto_src_trace_processor_db_storage_storage",
+        ":perfetto_src_trace_processor_db_storage_unittests",
         ":perfetto_src_trace_processor_db_unittests",
         ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
@@ -12027,17 +12122,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",
@@ -12115,6 +12212,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",
@@ -12682,6 +12780,8 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_db",
+        ":perfetto_src_trace_processor_db_overlays_overlays",
+        ":perfetto_src_trace_processor_db_storage_storage",
         ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
         ":perfetto_src_trace_processor_importers_common_common",
@@ -12711,13 +12811,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",
@@ -12905,6 +13007,8 @@
         ":perfetto_src_protozero_protozero",
         ":perfetto_src_trace_processor_containers_containers",
         ":perfetto_src_trace_processor_db_db",
+        ":perfetto_src_trace_processor_db_overlays_overlays",
+        ":perfetto_src_trace_processor_db_storage_storage",
         ":perfetto_src_trace_processor_export_json",
         ":perfetto_src_trace_processor_importers_android_bugreport_android_bugreport",
         ":perfetto_src_trace_processor_importers_common_common",
@@ -12934,11 +13038,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",
@@ -13043,15 +13149,22 @@
         "src/traced/service/main.cc",
     ],
     shared_libs: [
-        "liblog",
         "libperfetto",
     ],
+    host_supported: true,
     init_rc: [
         "perfetto.rc",
     ],
     defaults: [
         "perfetto_defaults",
     ],
+    target: {
+        android: {
+            shared_libs: [
+                "liblog",
+            ],
+        },
+    },
 }
 
 // GN: //src/profiling/perf:traced_perf
@@ -13236,9 +13349,9 @@
         "src/traced/probes/main.cc",
     ],
     shared_libs: [
-        "liblog",
         "libperfetto",
     ],
+    host_supported: true,
     defaults: [
         "perfetto_defaults",
     ],
@@ -13248,6 +13361,13 @@
         "traced_perf",
         "trigger_perfetto",
     ],
+    target: {
+        android: {
+            shared_libs: [
+                "liblog",
+            ],
+        },
+    },
 }
 
 // GN: //src/perfetto_cmd:trigger_perfetto
diff --git a/BUILD b/BUILD
index a8826c5..f217e0a 100644
--- a/BUILD
+++ b/BUILD
@@ -64,6 +64,154 @@
     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_db_overlays_overlays",
+        ":src_trace_processor_db_storage_storage",
+        ":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 +427,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 +504,7 @@
         ":protozero",
         ":src_base_base",
         ":src_base_version",
-    ],
+    ] + PERFETTO_CONFIG.deps.zlib,
     linkstatic = True,
 )
 
@@ -387,6 +536,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 +611,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 +933,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 +1034,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",
@@ -1075,6 +1273,32 @@
     linkstatic = True,
 )
 
+# GN target: //src/trace_processor/db/overlays:overlays
+perfetto_filegroup(
+    name = "src_trace_processor_db_overlays_overlays",
+    srcs = [
+        "src/trace_processor/db/overlays/null_overlay.cc",
+        "src/trace_processor/db/overlays/null_overlay.h",
+        "src/trace_processor/db/overlays/selector_overlay.cc",
+        "src/trace_processor/db/overlays/selector_overlay.h",
+        "src/trace_processor/db/overlays/storage_overlay.cc",
+        "src/trace_processor/db/overlays/storage_overlay.h",
+        "src/trace_processor/db/overlays/types.h",
+    ],
+)
+
+# GN target: //src/trace_processor/db/storage:storage
+perfetto_filegroup(
+    name = "src_trace_processor_db_storage_storage",
+    srcs = [
+        "src/trace_processor/db/storage/numeric_storage.cc",
+        "src/trace_processor/db/storage/numeric_storage.h",
+        "src/trace_processor/db/storage/storage.cc",
+        "src/trace_processor/db/storage/storage.h",
+        "src/trace_processor/db/storage/types.h",
+    ],
+)
+
 # GN target: //src/trace_processor/db:db
 perfetto_filegroup(
     name = "src_trace_processor_db_db",
@@ -1082,19 +1306,12 @@
         "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.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.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",
@@ -1588,6 +1805,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",
@@ -1801,10 +2019,10 @@
         "src/trace_processor/prelude/functions/import.h",
         "src/trace_processor/prelude/functions/layout_functions.cc",
         "src/trace_processor/prelude/functions/layout_functions.h",
+        "src/trace_processor/prelude/functions/math.cc",
+        "src/trace_processor/prelude/functions/math.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",
@@ -1816,6 +2034,15 @@
     ],
 )
 
+# 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",
@@ -1827,6 +2054,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",
@@ -1851,8 +2087,6 @@
         "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",
     ],
@@ -1924,17 +2158,35 @@
     ],
 )
 
+# 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",
     srcs = [
         "src/trace_processor/sqlite/db_sqlite_table.cc",
         "src/trace_processor/sqlite/db_sqlite_table.h",
+        "src/trace_processor/sqlite/perfetto_sql_engine.cc",
+        "src/trace_processor/sqlite/perfetto_sql_engine.h",
+        "src/trace_processor/sqlite/perfetto_sql_parser.cc",
+        "src/trace_processor/sqlite/perfetto_sql_parser.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_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_tokenizer.cc",
+        "src/trace_processor/sqlite/sqlite_tokenizer.h",
         "src/trace_processor/sqlite/sqlite_utils.cc",
         "src/trace_processor/sqlite/sqlite_utils.h",
         "src/trace_processor/sqlite/stats_table.cc",
@@ -1942,19 +2194,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",
@@ -1971,10 +2210,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",
     ],
 )
 
@@ -1982,7 +2224,10 @@
 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",
+        "src/trace_processor/stdlib/chrome/histograms.sql",
+        "src/trace_processor/stdlib/chrome/speedometer.sql",
     ],
 )
 
@@ -2063,6 +2308,7 @@
         "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/sched_tables.py",
         "src/trace_processor/tables/slice_tables.py",
         "src/trace_processor/tables/trace_proto_tables.py",
         "src/trace_processor/tables/track_tables.py",
@@ -2074,6 +2320,7 @@
         "src/trace_processor/tables/memory_tables_py.h",
         "src/trace_processor/tables/metadata_tables_py.h",
         "src/trace_processor/tables/profiler_tables_py.h",
+        "src/trace_processor/tables/sched_tables_py.h",
         "src/trace_processor/tables/slice_tables_py.h",
         "src/trace_processor/tables/trace_proto_tables_py.h",
         "src/trace_processor/tables/track_tables_py.h",
@@ -2632,6 +2879,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",
@@ -3044,6 +3300,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",
@@ -3052,6 +3333,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",
@@ -4193,6 +4482,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",
@@ -4724,6 +5021,8 @@
     srcs = [
         ":src_kernel_utils_syscall_table",
         ":src_trace_processor_db_db",
+        ":src_trace_processor_db_overlays_overlays",
+        ":src_trace_processor_db_storage_storage",
         ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
         ":src_trace_processor_importers_common_common",
@@ -4753,12 +5052,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",
@@ -4878,6 +5179,8 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_db",
+        ":src_trace_processor_db_overlays_overlays",
+        ":src_trace_processor_db_storage_storage",
         ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
         ":src_trace_processor_importers_common_common",
@@ -4907,14 +5210,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",
@@ -5091,6 +5396,8 @@
         ":src_profiling_symbolizer_symbolizer",
         ":src_protozero_proto_ring_buffer",
         ":src_trace_processor_db_db",
+        ":src_trace_processor_db_overlays_overlays",
+        ":src_trace_processor_db_storage_storage",
         ":src_trace_processor_export_json",
         ":src_trace_processor_importers_android_bugreport_android_bugreport",
         ":src_trace_processor_importers_common_common",
@@ -5120,12 +5427,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",
diff --git a/CHANGELOG b/CHANGELOG
index 6e371a4..107a26b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
 Unreleased:
   Tracing service and probes:
+    * 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:
     *
@@ -10,6 +14,8 @@
   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:
diff --git a/docs/contributing/common-tasks.md b/docs/contributing/common-tasks.md
index 4189d19..7c89e0e 100644
--- a/docs/contributing/common-tasks.md
+++ b/docs/contributing/common-tasks.md
@@ -169,3 +169,22 @@
 * There is no way to tie newly added events back to the source events in the
   trace which were used to generate them. This is not currently a priority but
   something we may add in the future.
+
+
+## Update `TRACE_PROCESSOR_CURRENT_API_VERSION`
+
+Generally you do not have to worry about version skew between the UI
+and the `trace_processor` since they are built together at the same
+commit. However version skew can occur when using the `--httpd` mode
+which allows a native `trace_processor` instance to be used with the UI.
+
+A common case is when the UI is more recent than `trace_processor`
+and depends on a new table definition. With older versions of
+`trace_processor` in `--httpd` mode the UI crashes attempting to query
+a non-existant table. To avoid this we use a version number. If the
+version number `trace_processor` reports is older than the one the UI
+was built with we prompt the user to update.
+
+1. Go to `protos/perfetto/trace_processor/trace_processor.proto`
+2. Increment `TRACE_PROCESSOR_CURRENT_API_VERSION`
+3. Add a comment explaining what has changed.
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/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/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/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/include/perfetto/base/status.h b/include/perfetto/base/status.h
index 2939357..f059184 100644
--- a/include/perfetto/base/status.h
+++ b/include/perfetto/base/status.h
@@ -17,7 +17,10 @@
 #ifndef INCLUDE_PERFETTO_BASE_STATUS_H_
 #define INCLUDE_PERFETTO_BASE_STATUS_H_
 
+#include <optional>
 #include <string>
+#include <string_view>
+#include <vector>
 
 #include "perfetto/base/compiler.h"
 #include "perfetto/base/export.h"
@@ -30,6 +33,10 @@
 // This can used as the return type of functions which would usually return an
 // bool for success or int for errno but also wants to add some string context
 // (ususally for logging).
+//
+// Similar to absl::Status, an optional "payload" can also be included with more
+// context about the error. This allows passing additional metadata about the
+// error (e.g. location of errors, potential mitigations etc).
 class PERFETTO_EXPORT_COMPONENT Status {
  public:
   Status() : ok_(true) {}
@@ -52,9 +59,49 @@
   const std::string& message() const { return message_; }
   const char* c_message() const { return message_.c_str(); }
 
+  //////////////////////////////////////////////////////////////////////////////
+  // Payload Management APIs
+  //////////////////////////////////////////////////////////////////////////////
+
+  // Payloads can be attached to error statuses to provide additional context.
+  //
+  // Payloads are (key, value) pairs, where the key is a string acting as a
+  // unique "type URL" and the value is an opaque string. The "type URL" should
+  // be unique, follow the format of a URL and, ideally, documentation on how to
+  // interpret its associated data should be available.
+  //
+  // To attach a payload to a status object, call `Status::SetPayload()`.
+  // Similarly, to extract the payload from a status, call
+  // `Status::GetPayload()`.
+  //
+  // Note: the payload APIs are only meaningful to call when the status is an
+  // error. Otherwise, all methods are noops.
+
+  // Gets the payload for the given |type_url| if one exists.
+  //
+  // Will always return std::nullopt if |ok()|.
+  std::optional<std::string_view> GetPayload(std::string_view type_url);
+
+  // Sets the payload for the given key. The key should
+  //
+  // Will always do nothing if |ok()|.
+  void SetPayload(std::string_view type_url, std::string value);
+
+  // Erases the payload for the given string and returns true if the payload
+  // existed and was erased.
+  //
+  // Will always do nothing if |ok()|.
+  bool ErasePayload(std::string_view type_url);
+
  private:
+  struct Payload {
+    std::string type_url;
+    std::string payload;
+  };
+
   bool ok_ = false;
   std::string message_;
+  std::vector<Payload> payloads_;
 };
 
 // Returns a status object which represents the Ok status.
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/data_source_type.h b/include/perfetto/tracing/internal/data_source_type.h
index aee830a..73c84be 100644
--- a/include/perfetto/tracing/internal/data_source_type.h
+++ b/include/perfetto/tracing/internal/data_source_type.h
@@ -167,8 +167,7 @@
   InstancesIterator BeginIteration(
       uint32_t cached_instances,
       DataSourceThreadLocalState* tls_state,
-      typename TracePointTraits::TracePointData trace_point_data)
-      PERFETTO_ALWAYS_INLINE {
+      typename TracePointTraits::TracePointData trace_point_data) {
     InstancesIterator it{};
     it.cached_instances = cached_instances;
     FirstActiveInstance<TracePointTraits>(&it, tls_state, trace_point_data);
@@ -185,8 +184,7 @@
   template <typename TracePointTraits>
   void NextIteration(InstancesIterator* iterator,
                      DataSourceThreadLocalState* tls_state,
-                     typename TracePointTraits::TracePointData trace_point_data)
-      PERFETTO_ALWAYS_INLINE {
+                     typename TracePointTraits::TracePointData trace_point_data) {
     iterator->i++;
     FirstActiveInstance<TracePointTraits>(iterator, tls_state,
                                           trace_point_data);
diff --git a/include/perfetto/tracing/internal/track_event_data_source.h b/include/perfetto/tracing/internal/track_event_data_source.h
index d1e17a6..2ec348e 100644
--- a/include/perfetto/tracing/internal/track_event_data_source.h
+++ b/include/perfetto/tracing/internal/track_event_data_source.h
@@ -308,276 +308,25 @@
         {category_index});
   }
 
-  // Once we've determined tracing to be enabled for this category, actually
-  // write a trace event onto this thread's default track. Outlined to avoid
-  // bloating code (mostly stack depth) at the actual trace point.
-  //
-  // The following combination of parameters is supported (in the given order):
-  // - Zero or one track,
-  // - Zero or one custom timestamp,
-  // - Arbitrary number of debug annotations.
-  // - Zero or one lambda.
-
-  // Trace point which does not take a track or timestamp.
-  template <typename CategoryType,
-            typename EventNameType,
-            typename... Arguments>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType& event_name,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(instances, category, event_name, type,
-                         TrackEventInternal::kDefaultTrack,
-                         TrackEventInternal::GetTraceTime(),
-                         std::forward<Arguments>(args)...);
+  // The following methods forward all arguments to TraceForCategoryBody
+  // while casting string constants to const char*.
+  template <typename... Arguments>
+  static void TraceForCategory(Arguments&&... args) PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryBody(DecayStrType(args)...);
   }
 
-  // Trace point which takes a track, but not timestamp.
-  // NOTE: Here track should be captured using universal reference (TrackType&&)
-  // instead of const TrackType& to ensure that the proper overload is selected
-  // (otherwise the compiler will fail to disambiguate between adding const& and
-  // parsing track as a part of Arguments...).
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType& event_name,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               TrackType&& track,
-                               Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(
-        instances, category, event_name, type, std::forward<TrackType>(track),
-        TrackEventInternal::GetTraceTime(), std::forward<Arguments>(args)...);
+  template <typename... Arguments>
+  static void TraceForCategoryLegacy(Arguments&&... args)
+      PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryLegacyBody(DecayStrType(args)...);
   }
 
-  // Trace point which takes a timestamp, but not track.
-  template <typename CategoryType,
-            typename EventNameType,
-            typename TimestampType = uint64_t,
-            typename... Arguments,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType& event_name,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               TimestampType&& timestamp,
-                               Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(instances, category, event_name, type,
-                         TrackEventInternal::kDefaultTrack,
-                         std::forward<TimestampType>(timestamp),
-                         std::forward<Arguments>(args)...);
+  template <typename... Arguments>
+  static void TraceForCategoryLegacyWithId(Arguments&&... args)
+      PERFETTO_ALWAYS_INLINE {
+    TraceForCategoryLegacyWithIdBody(DecayStrType(args)...);
   }
 
-  // Trace point which takes a timestamp and a track.
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename TimestampType = uint64_t,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType& event_name,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               TrackType&& track,
-                               TimestampType&& timestamp,
-                               Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(instances, category, event_name, type,
-                         std::forward<TrackType>(track),
-                         std::forward<TimestampType>(timestamp),
-                         std::forward<Arguments>(args)...);
-  }
-
-  // Trace point with with a counter sample.
-  template <typename CategoryType, typename EventNameType, typename ValueType>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType&,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               CounterTrack track,
-                               ValueType value) PERFETTO_ALWAYS_INLINE {
-    PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
-    TraceForCategory(instances, category, /*name=*/nullptr, type, track,
-                     TrackEventInternal::GetTraceTime(), value);
-  }
-
-  // Trace point with with a timestamp and a counter sample.
-  template <typename CategoryType,
-            typename EventNameType,
-            typename TimestampType = uint64_t,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type,
-            typename ValueType>
-  static void TraceForCategory(uint32_t instances,
-                               const CategoryType& category,
-                               const EventNameType&,
-                               perfetto::protos::pbzero::TrackEvent::Type type,
-                               CounterTrack track,
-                               TimestampType timestamp,
-                               ValueType value) PERFETTO_ALWAYS_INLINE {
-    PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
-    TraceForCategoryImpl(
-        instances, category, /*name=*/nullptr, type, track, timestamp,
-        [&](EventContext event_ctx) {
-          if (std::is_integral<ValueType>::value) {
-            int64_t value_int64 = static_cast<int64_t>(value);
-            if (track.is_incremental()) {
-              TrackEventIncrementalState* incr_state =
-                  event_ctx.GetIncrementalState();
-              PERFETTO_DCHECK(incr_state != nullptr);
-              auto prv_value =
-                  incr_state->last_counter_value_per_track[track.uuid];
-              event_ctx.event()->set_counter_value(value_int64 - prv_value);
-              prv_value = value_int64;
-              incr_state->last_counter_value_per_track[track.uuid] = prv_value;
-            } else {
-              event_ctx.event()->set_counter_value(value_int64);
-            }
-          } else {
-            event_ctx.event()->set_double_counter_value(
-                static_cast<double>(value));
-          }
-        });
-  }
-
-// Additional trace points used in legacy macros.
-// It's possible to implement legacy macros using a common TraceForCategory,
-// by supplying a lambda that sets all necessary legacy fields. But this
-// results in a binary size bloat because every trace point generates its own
-// template instantiation with its own lambda. ICF can't eliminate those as
-// each lambda captures different variables and so the code is not completely
-// identical.
-// What we do instead is define additional TraceForCategoryLegacy templates
-// that take legacy arguments directly. Their instantiations can have the same
-// binary code for at least some macro invocations and so can be successfully
-// folded by the linker.
-#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type>
-  static void TraceForCategoryLegacy(
-      uint32_t instances,
-      const CategoryType& category,
-      const EventNameType& event_name,
-      perfetto::protos::pbzero::TrackEvent::Type type,
-      TrackType&& track,
-      char phase,
-      uint32_t flags,
-      Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(instances, category, event_name, type, track,
-                         TrackEventInternal::GetTraceTime(),
-                         [&](perfetto::EventContext ctx)
-                             PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
-                               using ::perfetto::internal::TrackEventLegacy;
-                               TrackEventLegacy::WriteLegacyEvent(
-                                   std::move(ctx), phase, flags, args...);
-                             });
-  }
-
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename TimestampType = uint64_t,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type>
-  static void TraceForCategoryLegacy(
-      uint32_t instances,
-      const CategoryType& category,
-      const EventNameType& event_name,
-      perfetto::protos::pbzero::TrackEvent::Type type,
-      TrackType&& track,
-      char phase,
-      uint32_t flags,
-      TimestampType&& timestamp,
-      Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(
-        instances, category, event_name, type, track, timestamp,
-        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
-          using ::perfetto::internal::TrackEventLegacy;
-          TrackEventLegacy::WriteLegacyEvent(std::move(ctx), phase, flags,
-                                             args...);
-        });
-  }
-
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename ThreadIdType,
-            typename LegacyIdType,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type>
-  static void TraceForCategoryLegacyWithId(
-      uint32_t instances,
-      const CategoryType& category,
-      const EventNameType& event_name,
-      perfetto::protos::pbzero::TrackEvent::Type type,
-      TrackType&& track,
-      char phase,
-      uint32_t flags,
-      ThreadIdType thread_id,
-      LegacyIdType legacy_id,
-      Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(
-        instances, category, event_name, type, track,
-        TrackEventInternal::GetTraceTime(),
-        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
-          using ::perfetto::internal::TrackEventLegacy;
-          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
-          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
-              std::move(ctx), phase, flags, trace_id, thread_id, args...);
-        });
-  }
-
-  template <typename TrackType,
-            typename CategoryType,
-            typename EventNameType,
-            typename ThreadIdType,
-            typename LegacyIdType,
-            typename TimestampType = uint64_t,
-            typename... Arguments,
-            typename TrackTypeCheck = typename std::enable_if<
-                std::is_convertible<TrackType, Track>::value>::type,
-            typename TimestampTypeCheck = typename std::enable_if<
-                IsValidTimestamp<TimestampType>()>::type>
-  static void TraceForCategoryLegacyWithId(
-      uint32_t instances,
-      const CategoryType& category,
-      const EventNameType& event_name,
-      perfetto::protos::pbzero::TrackEvent::Type type,
-      TrackType&& track,
-      char phase,
-      uint32_t flags,
-      ThreadIdType thread_id,
-      LegacyIdType legacy_id,
-      TimestampType&& timestamp,
-      Arguments&&... args) PERFETTO_NO_INLINE {
-    TraceForCategoryImpl(
-        instances, category, event_name, type, track, timestamp,
-        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
-          using ::perfetto::internal::TrackEventLegacy;
-          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
-          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
-              std::move(ctx), phase, flags, trace_id, thread_id, args...);
-        });
-  }
-#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
-
   // Initialize the track event library. Should be called before tracing is
   // enabled.
   static bool Register() {
@@ -638,6 +387,295 @@
   const protos::gen::TrackEventConfig& GetConfig() const { return config_; }
 
  private:
+  // The DecayStrType method is used to avoid unnecessary instantiations of
+  // templates on string constants of different sizes. Without it, strings
+  // of different lengths have different types: char[10], char[15] etc.
+  // DecayStrType forwards all types of arguments as is, with the exception
+  // of string constants which are all cast to const char*. This allows to
+  // avoid extra instantiations of TraceForCategory templates.
+  template <typename T>
+  static T&& DecayStrType(T&& t) {
+    return std::forward<T>(t);
+  }
+
+  static const char* DecayStrType(const char* t) { return t; }
+
+  // Once we've determined tracing to be enabled for this category, actually
+  // write a trace event onto this thread's default track. Outlined to avoid
+  // bloating code (mostly stack depth) at the actual trace point.
+  //
+  // The following combination of parameters is supported (in the given order):
+  // - Zero or one track,
+  // - Zero or one custom timestamp,
+  // - Arbitrary number of debug annotations.
+  // - Zero or one lambda.
+
+  // Trace point which does not take a track or timestamp.
+  template <typename CategoryType,
+            typename EventNameType,
+            typename... Arguments>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(instances, category, event_name, type,
+                         TrackEventInternal::kDefaultTrack,
+                         TrackEventInternal::GetTraceTime(),
+                         std::forward<Arguments>(args)...);
+  }
+
+  // Trace point which takes a track, but not timestamp.
+  // NOTE: Here track should be captured using universal reference (TrackType&&)
+  // instead of const TrackType& to ensure that the proper overload is selected
+  // (otherwise the compiler will fail to disambiguate between adding const& and
+  // parsing track as a part of Arguments...).
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, std::forward<TrackType>(track),
+        TrackEventInternal::GetTraceTime(), std::forward<Arguments>(args)...);
+  }
+
+  // Trace point which takes a timestamp, but not track.
+  template <typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(instances, category, event_name, type,
+                         TrackEventInternal::kDefaultTrack,
+                         std::forward<TimestampType>(timestamp),
+                         std::forward<Arguments>(args)...);
+  }
+
+  // Trace point which takes a timestamp and a track.
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(instances, category, event_name, type,
+                         std::forward<TrackType>(track),
+                         std::forward<TimestampType>(timestamp),
+                         std::forward<Arguments>(args)...);
+  }
+
+  // Trace point with with a counter sample.
+  template <typename CategoryType, typename EventNameType, typename ValueType>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType&,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      CounterTrack track,
+      ValueType value) PERFETTO_ALWAYS_INLINE {
+    PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
+    TraceForCategory(instances, category, /*name=*/nullptr, type, track,
+                     TrackEventInternal::GetTraceTime(), value);
+  }
+
+  // Trace point with with a timestamp and a counter sample.
+  template <typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type,
+            typename ValueType>
+  static void TraceForCategoryBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType&,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      CounterTrack track,
+      TimestampType timestamp,
+      ValueType value) PERFETTO_ALWAYS_INLINE {
+    PERFETTO_DCHECK(type == perfetto::protos::pbzero::TrackEvent::TYPE_COUNTER);
+    TraceForCategoryImpl(
+        instances, category, /*name=*/nullptr, type, track, timestamp,
+        [&](EventContext event_ctx) {
+          if (std::is_integral<ValueType>::value) {
+            int64_t value_int64 = static_cast<int64_t>(value);
+            if (track.is_incremental()) {
+              TrackEventIncrementalState* incr_state =
+                  event_ctx.GetIncrementalState();
+              PERFETTO_DCHECK(incr_state != nullptr);
+              auto prv_value =
+                  incr_state->last_counter_value_per_track[track.uuid];
+              event_ctx.event()->set_counter_value(value_int64 - prv_value);
+              prv_value = value_int64;
+              incr_state->last_counter_value_per_track[track.uuid] = prv_value;
+            } else {
+              event_ctx.event()->set_counter_value(value_int64);
+            }
+          } else {
+            event_ctx.event()->set_double_counter_value(
+                static_cast<double>(value));
+          }
+        });
+  }
+
+// Additional trace points used in legacy macros.
+// It's possible to implement legacy macros using a common TraceForCategory,
+// by supplying a lambda that sets all necessary legacy fields. But this
+// results in a binary size bloat because every trace point generates its own
+// template instantiation with its own lambda. ICF can't eliminate those as
+// each lambda captures different variables and so the code is not completely
+// identical.
+// What we do instead is define additional TraceForCategoryLegacy templates
+// that take legacy arguments directly. Their instantiations can have the same
+// binary code for at least some macro invocations and so can be successfully
+// folded by the linker.
+#if PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryLegacyBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(instances, category, event_name, type, track,
+                         TrackEventInternal::GetTraceTime(),
+                         [&](perfetto::EventContext ctx)
+                             PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+                               using ::perfetto::internal::TrackEventLegacy;
+                               TrackEventLegacy::WriteLegacyEvent(
+                                   std::move(ctx), phase, flags, args...);
+                             });
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryLegacyBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, track, timestamp,
+        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+          using ::perfetto::internal::TrackEventLegacy;
+          TrackEventLegacy::WriteLegacyEvent(std::move(ctx), phase, flags,
+                                             args...);
+        });
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename ThreadIdType,
+            typename LegacyIdType,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type>
+  static void TraceForCategoryLegacyWithIdBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      ThreadIdType thread_id,
+      LegacyIdType legacy_id,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, track,
+        TrackEventInternal::GetTraceTime(),
+        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+          using ::perfetto::internal::TrackEventLegacy;
+          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
+          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
+              std::move(ctx), phase, flags, trace_id, thread_id, args...);
+        });
+  }
+
+  template <typename TrackType,
+            typename CategoryType,
+            typename EventNameType,
+            typename ThreadIdType,
+            typename LegacyIdType,
+            typename TimestampType = uint64_t,
+            typename... Arguments,
+            typename TrackTypeCheck = typename std::enable_if<
+                std::is_convertible<TrackType, Track>::value>::type,
+            typename TimestampTypeCheck = typename std::enable_if<
+                IsValidTimestamp<TimestampType>()>::type>
+  static void TraceForCategoryLegacyWithIdBody(
+      uint32_t instances,
+      const CategoryType& category,
+      const EventNameType& event_name,
+      perfetto::protos::pbzero::TrackEvent::Type type,
+      TrackType&& track,
+      char phase,
+      uint32_t flags,
+      ThreadIdType thread_id,
+      LegacyIdType legacy_id,
+      TimestampType&& timestamp,
+      Arguments&&... args) PERFETTO_NO_INLINE {
+    TraceForCategoryImpl(
+        instances, category, event_name, type, track, timestamp,
+        [&](perfetto::EventContext ctx) PERFETTO_NO_THREAD_SAFETY_ANALYSIS {
+          using ::perfetto::internal::TrackEventLegacy;
+          ::perfetto::internal::LegacyTraceId trace_id{legacy_id};
+          TrackEventLegacy::WriteLegacyEventWithIdAndTid(
+              std::move(ctx), phase, flags, trace_id, thread_id, args...);
+        });
+  }
+#endif  // PERFETTO_ENABLE_LEGACY_TRACE_EVENTS
+
   // Each category has its own enabled/disabled state, stored in the category
   // registry.
   struct CategoryTracePointTraits {
diff --git a/infra/perfetto.dev/src/gen_stdlib_docs_md.py b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
index efb52a2..e52dea0 100644
--- a/infra/perfetto.dev/src/gen_stdlib_docs_md.py
+++ b/infra/perfetto.dev/src/gen_stdlib_docs_md.py
@@ -20,14 +20,14 @@
 import argparse
 import sys
 import json
-from typing import List, Dict
+from typing import Any, List, Dict
 
 
 # Responsible for module level markdown generation.
 class ModuleMd:
 
-  def __init__(self, module_name: str,
-               module_files: List[Dict[str, any]]) -> None:
+  def __init__(self, module_name: str, module_files: List[Dict[str,
+                                                               Any]]) -> None:
     self.module_name = module_name
     self.files_md = [
         FileMd(module_name, file_dict) for file_dict in module_files
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/trace_stats.proto b/protos/perfetto/common/trace_stats.proto
index 4e6d455..bbee8f9 100644
--- a/protos/perfetto/common/trace_stats.proto
+++ b/protos/perfetto/common/trace_stats.proto
@@ -20,7 +20,7 @@
 
 // Statistics for the internals of the tracing service.
 //
-// Next id: 17.
+// Next id: 19.
 message TraceStats {
   // From TraceBuffer::Stats.
   //
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/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/monitor_contention_metric.proto b/protos/perfetto/metrics/android/monitor_contention_metric.proto
index 91122eb..00585d8 100644
--- a/protos/perfetto/metrics/android/monitor_contention_metric.proto
+++ b/protos/perfetto/metrics/android/monitor_contention_metric.proto
@@ -21,28 +21,38 @@
 // This metric provides information about the monitor contention graph in a
 // trace
 message AndroidMonitorContentionMetric {
+  // Next field id: 24
   message Node {
+    // Global context
     optional int64 node_parent_id = 1;
     optional int64 node_id = 2;
     optional int64 ts = 3;
     optional int64 dur = 4;
-    optional string blocking_method = 5;
-    optional string blocked_method = 6;
-    optional string short_blocking_method = 7;
-    optional string short_blocked_method = 8;
-    optional string blocking_src = 9;
-    optional string blocked_src = 10;
-    optional uint32 waiter_count = 11;
-    optional string blocked_thread_name = 12;
-    optional string blocking_thread_name = 13;
     optional string process_name = 14;
-    optional bool is_blocked_thread_main = 15;
-    optional bool is_blocking_thread_main = 16;
-    optional int64 binder_reply_ts = 17;
-    optional uint32 binder_reply_tid = 18;
-
+    optional uint32 pid = 23;
+    optional uint32 waiter_count = 11;
     repeated ThreadStateBreakdown thread_states = 19;
     repeated BlockedFunctionBreakdown blocked_functions = 20;
+
+    // Blocking context
+    optional string blocking_method = 5;
+    optional string short_blocking_method = 7;
+    optional string blocking_src = 9;
+    optional string blocking_thread_name = 13;
+    optional bool is_blocking_thread_main = 16;
+    optional uint32 blocking_thread_tid = 22;
+
+    // Blocked context
+    optional string blocked_method = 6;
+    optional string short_blocked_method = 8;
+    optional string blocked_src = 10;
+    optional string blocked_thread_name = 12;
+    optional bool is_blocked_thread_main = 15;
+    optional uint32 blocked_thread_tid = 21;
+
+    // Binder context
+    optional int64 binder_reply_ts = 17;
+    optional uint32 binder_reply_tid = 18;
   }
 
   message ThreadStateBreakdown {
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 95a2ff0..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: 34.
+  // Next id: 35.
   message ToFirstFrame {
     // The duration between the intent received and first frame.
     optional int64 dur_ns = 1;
@@ -121,6 +121,10 @@
     // 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;
 
@@ -220,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;
@@ -276,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/chrome/scroll_jank_v2.proto b/protos/perfetto/metrics/chrome/scroll_jank_v2.proto
index 9464b78..9b7919d 100644
--- a/protos/perfetto/metrics/chrome/scroll_jank_v2.proto
+++ b/protos/perfetto/metrics/chrome/scroll_jank_v2.proto
@@ -29,4 +29,14 @@
   optional double scroll_jank_processing_ms = 2 [(unit) = "ms_smallerIsBetter"];
   // Computed as: `100 * scroll_jank_processing_ms / scroll_processing_ms`.
   optional double scroll_jank_percentage = 3 [(unit) = "n%_smallerIsBetter"];
+  // The number of scroll janks. Similar to above, this excludes jank caused
+  // due to `RendererCompositorQueueingDelay`, which can add nontrivial noise.
+  optional int64 num_scroll_janks = 4 [(unit) = "count_smallerIsBetter"];
+  // The primary cause and duration for scroll jank, one for each jank.
+  // There are exactly `num_scroll_janks` items in this field.
+  message ScrollJankCauseAndDuration {
+    optional string cause = 1;
+    optional double duration_ms = 2 [(unit) = "ms_smallerIsBetter"];
+  }
+  repeated ScrollJankCauseAndDuration scroll_jank_causes_and_durations = 5;
 }
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 60ffcd4..393095c 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 {
@@ -1019,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
@@ -1038,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
@@ -1185,28 +1193,38 @@
 // This metric provides information about the monitor contention graph in a
 // trace
 message AndroidMonitorContentionMetric {
+  // Next field id: 24
   message Node {
+    // Global context
     optional int64 node_parent_id = 1;
     optional int64 node_id = 2;
     optional int64 ts = 3;
     optional int64 dur = 4;
-    optional string blocking_method = 5;
-    optional string blocked_method = 6;
-    optional string short_blocking_method = 7;
-    optional string short_blocked_method = 8;
-    optional string blocking_src = 9;
-    optional string blocked_src = 10;
-    optional uint32 waiter_count = 11;
-    optional string blocked_thread_name = 12;
-    optional string blocking_thread_name = 13;
     optional string process_name = 14;
-    optional bool is_blocked_thread_main = 15;
-    optional bool is_blocking_thread_main = 16;
-    optional int64 binder_reply_ts = 17;
-    optional uint32 binder_reply_tid = 18;
-
+    optional uint32 pid = 23;
+    optional uint32 waiter_count = 11;
     repeated ThreadStateBreakdown thread_states = 19;
     repeated BlockedFunctionBreakdown blocked_functions = 20;
+
+    // Blocking context
+    optional string blocking_method = 5;
+    optional string short_blocking_method = 7;
+    optional string blocking_src = 9;
+    optional string blocking_thread_name = 13;
+    optional bool is_blocking_thread_main = 16;
+    optional uint32 blocking_thread_tid = 22;
+
+    // Blocked context
+    optional string blocked_method = 6;
+    optional string short_blocked_method = 8;
+    optional string blocked_src = 10;
+    optional string blocked_thread_name = 12;
+    optional bool is_blocked_thread_main = 15;
+    optional uint32 blocked_thread_tid = 21;
+
+    // Binder context
+    optional int64 binder_reply_ts = 17;
+    optional uint32 binder_reply_tid = 18;
   }
 
   message ThreadStateBreakdown {
@@ -1591,7 +1609,7 @@
 
   // Timing information spanning the intent received by the
   // activity manager to the first frame drawn.
-  // Next id: 34.
+  // Next id: 35.
   message ToFirstFrame {
     // The duration between the intent received and first frame.
     optional int64 dur_ns = 1;
@@ -1665,6 +1683,10 @@
     // 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;
 
@@ -1764,7 +1786,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;
@@ -1820,6 +1842,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/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index d208b94..1681cd9 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
@@ -3205,7 +3240,7 @@
 
 // Statistics for the internals of the tracing service.
 //
-// Next id: 17.
+// Next id: 19.
 message TraceStats {
   // From TraceBuffer::Stats.
   //
@@ -11216,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;
 
@@ -11302,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..03759b4 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/trace_processor.proto b/protos/perfetto/trace_processor/trace_processor.proto
index 667cab0..6e4dad0 100644
--- a/protos/perfetto/trace_processor/trace_processor.proto
+++ b/protos/perfetto/trace_processor/trace_processor.proto
@@ -42,7 +42,9 @@
   // every time a new feature that the UI depends on is being introduced (e.g.
   // new tables, new SQL operators, metrics that are required by the UI).
   // See also StatusResult.api_version (below).
-  TRACE_PROCESSOR_CURRENT_API_VERSION = 6;
+  // Changes:
+  // 7. Introduce GUESS_CPU_SIZE
+  TRACE_PROCESSOR_CURRENT_API_VERSION = 7;
 }
 
 // At lowest level, the wire-format of the RPC procol is a linear sequence of
diff --git a/protos/third_party/chromium/chrome_track_event.proto b/protos/third_party/chromium/chrome_track_event.proto
index 1f5a211..cce5e76 100644
--- a/protos/third_party/chromium/chrome_track_event.proto
+++ b/protos/third_party/chromium/chrome_track_event.proto
@@ -513,7 +513,10 @@
   optional int32 browsing_instance_id = 1;
 
   // The ID of the CoopRelatedGroup that the BrowsingContextState belongs to.
-  optional int32 coop_related_group_id = 2;
+  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.
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/BUILD.gn b/python/BUILD.gn
index 06ebd9c..17ad6f3 100644
--- a/python/BUILD.gn
+++ b/python/BUILD.gn
@@ -24,10 +24,10 @@
 
 perfetto_py_library("trace_processor_stdlib_docs") {
   sources = [
+    "generators/stdlib_docs/extractor.py",
     "generators/stdlib_docs/parse.py",
-    "generators/stdlib_docs/stdlib.py",
+    "generators/stdlib_docs/types.py",
     "generators/stdlib_docs/utils.py",
-    "generators/stdlib_docs/validate.py",
   ]
 }
 
diff --git a/python/generators/stdlib_docs/extractor.py b/python/generators/stdlib_docs/extractor.py
new file mode 100644
index 0000000..94db4d3
--- /dev/null
+++ b/python/generators/stdlib_docs/extractor.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+# 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.
+
+from dataclasses import dataclass
+from re import Match
+from typing import List, Optional, Tuple
+
+from python.generators.stdlib_docs.types import ObjKind
+from python.generators.stdlib_docs.utils import extract_comment
+from python.generators.stdlib_docs.utils import match_pattern
+from python.generators.stdlib_docs.utils import PATTERN_BY_KIND
+
+
+class DocsExtractor:
+  """Extracts documentation for views/tables/functions from SQL."""
+  path: str
+  module_name: str
+  sql: str
+
+  @dataclass
+  class Annotation:
+    key: str
+    value: str
+
+  @dataclass
+  class Extract:
+    """Extracted documentation for a single view/table/function."""
+    obj_kind: ObjKind
+    obj_match: Match
+
+    description: str
+    annotations: List['DocsExtractor.Annotation']
+
+  def __init__(self, path: str, module_name: str, sql: str):
+    self.path = path
+    self.module_name = module_name
+    self.sql = sql
+
+    self.sql_lines = sql.split("\n")
+    self.errors = []
+
+  def extract(self) -> List[Extract]:
+    extracted = []
+    extracted += self._extract_for_kind(ObjKind.table_view)
+    extracted += self._extract_for_kind(ObjKind.function)
+    extracted += self._extract_for_kind(ObjKind.view_function)
+    return extracted
+
+  def _extract_for_kind(self, kind: ObjKind) -> List[Extract]:
+    line_number_to_matches = match_pattern(PATTERN_BY_KIND[kind], self.sql)
+    extracts = []
+    for line_number, match in sorted(list(line_number_to_matches.items())):
+      comment_lines = extract_comment(self.sql_lines, line_number)
+      e = self._extract_from_comment(kind, match, comment_lines)
+      if e:
+        extracts.append(e)
+    return extracts
+
+  def _extract_from_comment(self, kind: ObjKind, match: Match,
+                            comment_lines: List[str]) -> Optional[Extract]:
+    extract = DocsExtractor.Extract(kind, match, '', [])
+    for line in comment_lines:
+      assert line.startswith('--')
+
+      # Remove the comment.
+      stripped = line.lstrip('--').lstrip()
+
+      # Ignore lines which only contain '--'.
+      if not stripped:
+        continue
+
+      # Check if the line is an annotation.
+      if not stripped.startswith('@'):
+        # We are not in annotation: if we haven't seen an annotation yet, we
+        # must be still be parsing the description. Just add to that
+        if not extract.annotations:
+          extract.description += stripped + " "
+          continue
+
+        # Otherwise, add to the latest annotation.
+        extract.annotations[-1].value += " " + stripped
+        continue
+
+      # This line is an annotation: find its name and add a new entry
+      annotation, rest = stripped.split(' ', 1)
+      extract.annotations.append(DocsExtractor.Annotation(annotation, rest))
+    return extract
diff --git a/python/generators/stdlib_docs/parse.py b/python/generators/stdlib_docs/parse.py
index bdef717..a78bcca 100644
--- a/python/generators/stdlib_docs/parse.py
+++ b/python/generators/stdlib_docs/parse.py
@@ -13,102 +13,261 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from abc import ABC
+from dataclasses import dataclass
 import re
-from typing import Union, List, Tuple
+import sys
+from typing import Any, Dict, List, Optional, Set, Tuple, Union
 
-from python.generators.stdlib_docs import stdlib
-from python.generators.stdlib_docs.utils import Errors, Pattern, get_text, fetch_comment, match_pattern
+from python.generators.stdlib_docs.extractor import DocsExtractor
+from python.generators.stdlib_docs.types import ObjKind
+from python.generators.stdlib_docs.utils import ARG_ANNOTATION_PATTERN
+from python.generators.stdlib_docs.utils import NAME_AND_TYPE_PATTERN
+from python.generators.stdlib_docs.utils import FUNCTION_RETURN_PATTERN
+from python.generators.stdlib_docs.utils import COLUMN_ANNOTATION_PATTERN
 
 
-def parse_desc(docs: 'stdlib.AnyDocs') -> str:
-  desc_lines = [get_text(line, False) for line in docs.desc]
-  return ' '.join(desc_lines).strip('\n').strip()
+class AbstractDocParser(ABC):
 
+  @dataclass
+  class Column:
+    pass
 
-# Whether comment segment about columns contain proper schema. Can be matched
-# against parsed SQL data by setting `use_data_from_sql`.
-def parse_columns(docs: Union['stdlib.TableViewDocs', 'stdlib.ViewFunctionDocs']
-                 ) -> dict:
-  cols = {}
-  last_col = None
-  last_desc = []
-  for line in docs.columns:
-    # Ignore only '--' line.
-    if line == "--" or not line.startswith("-- @column"):
-      last_desc.append(get_text(line))
-      continue
+  def __init__(self, path: str, module: str):
+    self.path = path
+    self.module = module
+    self.name = None
+    self.errors = []
 
-    # Look for '-- @column' line as a column description
-    m = re.match(Pattern['column'], line)
-    if last_col:
-      cols[last_col] = ' '.join(last_desc)
+  def _parse_name(self, upper: bool = False):
+    assert self.name
+    assert isinstance(self.name, str)
+    module_pattern = f"^{self.module}_.*"
+    if upper:
+      module_pattern = module_pattern.upper()
+    starts_with_module_name = re.match(module_pattern, self.name)
+    if self.module == "common":
+      if starts_with_module_name:
+        self._error('Names of tables/views/functions in the "common" module '
+                    f'should not start with {module_pattern}')
+      return self.name
+    if not starts_with_module_name:
+      self._error('Names of tables/views/functions should be prefixed with the '
+                  f'module name (i.e. should start with {module_pattern})')
+    return self.name.strip()
+
+  def _parse_desc_not_empty(self, desc: str):
+    if not desc:
+      self._error('Description of the table/view/function is missing')
+    return desc.strip()
+
+  def _validate_only_contains_annotations(self,
+                                          ans: List[DocsExtractor.Annotation],
+                                          ans_types: Set[str]):
+    used_ans_types = set(a.key for a in ans)
+    for type in used_ans_types.difference(ans_types):
+      self._error(f'Unknown documentation annotation {type}')
+
+  def _parse_columns(self, ans: List[DocsExtractor.Annotation],
+                     sql_cols_str: str) -> Dict[str, str]:
+    cols = {}
+    for t in ans:
+      if t.key != '@column':
+        continue
+      m = re.match(COLUMN_ANNOTATION_PATTERN, t.value)
+      if not m:
+        self._error(f'@column annotation value {t.value} does not match '
+                    f'pattern {COLUMN_ANNOTATION_PATTERN}')
+        continue
+      cols[m.group(1)] = m.group(2).strip()
+
+    sql_cols = self._parse_name_and_types_str(sql_cols_str)
+    if sql_cols:
+      for col in set(cols.keys()).difference(sql_cols.keys()):
+        self._error(f'@column "{col}" documented but does not exist in '
+                    'function definition')
+      for col in set(sql_cols.keys()).difference(cols):
+        self._error(f'Column "{col}" defined in SQL but is not documented with '
+                    '@column')
+    return cols
+
+  def _parse_args(self, ans: List[DocsExtractor.Annotation],
+                  sql_args_str: str) -> Dict[str, Any]:
+    args = {}
+    for an in ans:
+      if an.key != '@arg':
+        continue
+      m = re.match(ARG_ANNOTATION_PATTERN, an.value)
+      if m is None:
+        self._error(f'Expected arg documentation "{an.value}" to match pattern '
+                    f'{ARG_ANNOTATION_PATTERN}')
+        continue
+      args[m.group(1)] = {'type': m.group(2), 'desc': m.group(3).strip()}
+
+    sql_args = self._parse_name_and_types_str(sql_args_str)
+    if sql_args:
+      for col in set(args.keys()).difference(sql_args.keys()):
+        self._error(f'Arg "{col}" documented with @arg but does not exist '
+                    'in function definition')
+      for arg in set(sql_args.keys()).difference(args.keys()):
+        self._error(f'Arg "{arg}" defined in SQL but is not documented with '
+                    '@arg')
+    return args
+
+  def _parse_ret(self, ans: List[DocsExtractor.Annotation],
+                 sql_ret_type: str) -> Tuple[str, str]:
+    rets = [a.value for a in ans if a.key == '@ret']
+    if len(rets) != 1:
+      self._error('Return value is not documentated with @ret')
+      return '', ''
+
+    ret = rets[0]
+    m = re.match(FUNCTION_RETURN_PATTERN, ret)
     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)]
+      self._error(
+          f'@ret {ret} does not match pattern {FUNCTION_RETURN_PATTERN}')
+      return '', ''
 
-  cols[last_col] = ' '.join(last_desc)
-  return cols
+    ret_type, ret_desc = m.group(1), m.group(2)
+    if ret_type != sql_ret_type:
+      self._error(
+          f'@ret {ret_type} does not match SQL return type {sql_ret_type}')
+      return '', ''
+    return ret_type, ret_desc.strip()
+
+  def _parse_name_and_types_str(self, args_str: str) -> Dict[str, str]:
+    if not args_str:
+      return {}
+
+    args = {}
+    for arg_str in args_str.split(","):
+      m = re.match(NAME_AND_TYPE_PATTERN, arg_str)
+      if m is None:
+        self._error(f'Expected "{arg_str}" to match pattern '
+                    f'{NAME_AND_TYPE_PATTERN}')
+        continue
+      args[m.group(1)] = m.group(2).strip()
+    return args
+
+  def _error(self, error: str):
+    self.errors.append(
+        f'Error while parsing documentation for {self.name} in {self.path}: '
+        f'{error}')
 
 
-def parse_args(docs: "stdlib.FunctionDocs") -> dict:
-  if not docs.args:
-    return {}
+class TableViewDocParser(AbstractDocParser):
+  """Parses documentation for CREATE TABLE and CREATE VIEW statements."""
 
-  args = {}
-  last_arg, last_desc, last_type = None, [], None
-  for line in docs.args:
-    # Ignore only '--' line.
-    if line == "--" or not line.startswith("-- @arg"):
-      last_desc.append(get_text(line))
-      continue
+  def __init__(self, path: str, module: str):
+    super().__init__(path, module)
 
-    m = re.match(Pattern['args'], line)
-    if last_arg:
-      args[last_arg] = {'type': last_type, 'desc': ' '.join(last_desc)}
-    last_arg, last_type, last_desc = m.group(1), m.group(2), [m.group(3)]
+  def parse(self, doc: DocsExtractor.Extract) -> Optional[Dict[str, Any]]:
+    assert doc.obj_kind == ObjKind.table_view
 
-  args[last_arg] = {'type': last_type, 'desc': ' '.join(last_desc)}
-  return args
+    # Ignore internal tables and views.
+    self.name = doc.obj_match[1]
+    if re.match(r'^internal_.*', self.name):
+      return None
+
+    self._validate_only_contains_annotations(doc.annotations, {'@column'})
+    return {
+        'name': self._parse_name(),
+        'type': doc.obj_match[0],
+        'desc': self._parse_desc_not_empty(doc.description),
+        'cols': self._parse_columns(doc.annotations, ''),
+    }
 
 
-# Whether comment segment about return contain proper schema. Matches against
-# parsed SQL data.
-def parse_ret(docs: "stdlib.FunctionDocs") -> Tuple[str, str]:
-  desc = []
-  for line in docs.ret:
-    # Ignore only '--' line.
-    if line == "--" or not line.startswith("-- @ret"):
-      desc.append(get_text(line))
+class FunctionDocParser(AbstractDocParser):
+  """Parses documentation for CREATE_FUNCTION statements."""
 
-    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))
+  def __init__(self, path: str, module: str):
+    super().__init__(path, module)
+
+  def parse(self, doc: DocsExtractor.Extract) -> Optional[Dict[str, Any]]:
+    self.name, args, ret, _ = doc.obj_match
+
+    # Ignore internal functions.
+    if re.match(r'^INTERNAL_.*', self.name):
+      return None
+
+    self._validate_only_contains_annotations(doc.annotations, {'@arg', '@ret'})
+
+    ret_type, ret_desc = self._parse_ret(doc.annotations, ret)
+    return {
+        'name': self._parse_name(upper=True),
+        'desc': self._parse_desc_not_empty(doc.description),
+        'args': self._parse_args(doc.annotations, args),
+        'return_type': ret_type,
+        'return_desc': ret_desc,
+    }
 
 
-# After matching file to Pattern, fetches and validates related documentation.
-def parse_typed_docs(path: str, module: str, sql: str, Pattern: str,
-                     docs_object: type
-                    ) -> Tuple[List['stdlib.AnyDocs'], Errors]:
+class ViewFunctionDocParser(AbstractDocParser):
+  """Parses documentation for CREATE_VIEW_FUNCTION statements."""
+
+  def __init__(self, path: str, module: str):
+    super().__init__(path, module)
+
+  def parse(self, doc: DocsExtractor.Extract) -> Optional[Dict[str, Any]]:
+    self.name, args, columns, _ = doc.obj_match
+
+    # Ignore internal functions.
+    if re.match(r'^INTERNAL_.*', self.name):
+      return None
+
+    self._validate_only_contains_annotations(doc.annotations,
+                                             {'@arg', '@column'})
+    return {
+        'name': self._parse_name(upper=True),
+        'desc': self._parse_desc_not_empty(doc.description),
+        'cols': self._parse_columns(doc.annotations, columns),
+        'args': self._parse_args(doc.annotations, args),
+    }
+
+
+# Reads the provided SQL and, if possible, generates a dictionary with data
+# from documentation together with errors from validation of the schema.
+def parse_file_to_dict(path: str, sql: str) -> Union[Dict[str, Any], List[str]]:
+  if sys.platform.startswith('win'):
+    path = path.replace('\\', '/')
+
+  # Get module name
+  module_name = path.split('/stdlib/')[-1].split('/')[0]
+
+  # Extract all the docs from the SQL.
+  extractor = DocsExtractor(path, module_name, sql)
+  docs = extractor.extract()
+  if extractor.errors:
+    return extractor.errors
+
+  # Parse the extracted docs.
   errors = []
-  line_id_to_match = match_pattern(Pattern, sql)
-  lines = sql.split("\n")
-  all_typed_docs = []
-  for line_id, matches in line_id_to_match.items():
-    # Fetch comment by looking at lines over beginning of match in reverse
-    # order.
-    comment = fetch_comment(lines[line_id - 1::-1])
-    typed_docs, obj_errors = docs_object.create_from_comment(
-        path, comment, module, matches)
-    errors += obj_errors
+  table_views = []
+  functions = []
+  view_functions = []
+  for doc in docs:
+    if doc.obj_kind == ObjKind.table_view:
+      parser = TableViewDocParser(path, module_name)
+      res = parser.parse(doc)
+      if res:
+        table_views.append(res)
+      errors += parser.errors
+    if doc.obj_kind == ObjKind.function:
+      parser = FunctionDocParser(path, module_name)
+      res = parser.parse(doc)
+      if res:
+        functions.append(res)
+      errors += parser.errors
+    if doc.obj_kind == ObjKind.view_function:
+      parser = ViewFunctionDocParser(path, module_name)
+      res = parser.parse(doc)
+      if res:
+        view_functions.append(res)
+      errors += parser.errors
 
-    if not typed_docs:
-      continue
-
-    errors += typed_docs.check_comment()
-
-    if not errors:
-      all_typed_docs.append(typed_docs)
-
-  return all_typed_docs, errors
+  return errors if errors else {
+      'imports': table_views,
+      'functions': functions,
+      'view_functions': view_functions
+  }
diff --git a/python/generators/stdlib_docs/stdlib.py b/python/generators/stdlib_docs/stdlib.py
deleted file mode 100644
index bf35d55..0000000
--- a/python/generators/stdlib_docs/stdlib.py
+++ /dev/null
@@ -1,343 +0,0 @@
-#!/usr/bin/env python3
-# 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.
-
-# This tool checks that every SQL object created without prefix
-# 'internal_' is documented with proper schema.
-
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
-import os
-import re
-import sys
-from typing import Union, List, Tuple, Dict
-from dataclasses import dataclass
-
-from python.generators.stdlib_docs.utils import *
-from python.generators.stdlib_docs.validate import *
-from python.generators.stdlib_docs.parse import *
-
-CommentLines = List[str]
-AnyDocs = Union['TableViewDocs', 'FunctionDocs', 'ViewFunctionDocs']
-
-
-# Stores documentation for CREATE {TABLE|VIEW} with comment split into
-# segments.
-@dataclass
-class TableViewDocs:
-  name: str
-  obj_type: str
-  desc: CommentLines
-  columns: CommentLines
-  path: str
-
-  # Contructs new TableViewDocs from the entire comment, by splitting it on
-  # typed lines. Returns None for improperly structured schemas.
-  @staticmethod
-  def create_from_comment(path: str, comment: CommentLines, module: str,
-                          matches: Tuple) -> Tuple['TableViewDocs', Errors]:
-    obj_type, name = matches[:2]
-
-    # Ignore internal tables and views.
-    if re.match(r"^internal_.*", name):
-      return None, []
-
-    errors = validate_name(name, module)
-    col_start = None
-    has_desc = False
-
-    # Splits code into segments by finding beginning of column segment.
-    for i, line in enumerate(comment):
-      # Ignore only '--' line.
-      if line == "--":
-        continue
-
-      m = re.match(Pattern['typed_line'], line)
-
-      # Ignore untyped lines
-      if not m:
-        if not col_start:
-          has_desc = True
-        continue
-
-      line_type = m.group(1)
-      if line_type == "column" and not col_start:
-        col_start = i
-        continue
-
-    if not has_desc:
-      errors.append(f"No description for {obj_type}: '{name}' in {path}'\n")
-      return None, errors
-
-    if not col_start:
-      errors.append(f"No columns for {obj_type}: '{name}' in {path}'\n")
-      return None, errors
-
-    return (
-        TableViewDocs(name, obj_type, comment[:col_start], comment[col_start:],
-                      path),
-        errors,
-    )
-
-  def check_comment(self) -> Errors:
-    return validate_columns(self)
-
-  def parse_comment(self) -> dict:
-    return {
-        'name': self.name,
-        'type': self.obj_type,
-        'desc': parse_desc(self),
-        'cols': parse_columns(self)
-    }
-
-
-# Stores documentation for create_function with comment split into segments.
-class FunctionDocs:
-
-  def __init__(
-      self,
-      path: str,
-      data_from_sql: dict,
-      module: str,
-      name: str,
-      desc: str,
-      args: CommentLines,
-      ret: CommentLines,
-  ):
-    self.path = path
-    self.data_from_sql = data_from_sql
-    self.module = module
-    self.name = name
-    self.desc = desc
-    self.args = args
-    self.ret = ret
-
-  # Contructs new FunctionDocs from whole comment, by splitting it on typed
-  # lines. Returns None for improperly structured schemas.
-  @staticmethod
-  def create_from_comment(path: str, comment: CommentLines, module: str,
-                          matches: Tuple) -> Tuple['FunctionDocs', Errors]:
-    name, args, ret, sql = matches
-
-    # Ignore internal functions.
-    if re.match(r"^INTERNAL_.*", name):
-      return None, []
-
-    errors = validate_name(name, module, upper=True)
-    has_desc, start_args, start_ret = False, None, None
-
-    args_dict, parse_errors = parse_args_str(args)
-    errors += parse_errors
-
-    # Splits code into segments by finding beginning of args and ret segments.
-    for i, line in enumerate(comment):
-      # Ignore only '--' line.
-      if line == "--":
-        continue
-
-      m = re.match(Pattern['typed_line'], line)
-
-      # Ignore untyped lines
-      if not m:
-        if not start_args:
-          has_desc = True
-        continue
-
-      line_type = m.group(1)
-      if line_type == "arg" and not start_args:
-        start_args = i
-        continue
-
-      if line_type == "ret" and not start_ret:
-        start_ret = i
-        continue
-
-    if not has_desc:
-      errors.append(f"No description for '{name}' in {path}'\n")
-      return None, errors
-
-    if not start_ret or (args_dict and not start_args):
-      errors.append(f"Function requires 'arg' and 'ret' comments.\n"
-                    f"'{name}' in {path}\n")
-      return None, errors
-
-    if not args_dict:
-      start_args = start_ret
-
-    data_from_sql = {'name': name, 'args': args_dict, 'ret': ret, 'sql': sql}
-    return (
-        FunctionDocs(
-            path,
-            data_from_sql,
-            module,
-            name,
-            comment[:start_args],
-            comment[start_args:start_ret] if args_dict else None,
-            comment[start_ret:],
-        ),
-        errors,
-    )
-
-  def check_comment(self) -> Errors:
-    errors = validate_args(self)
-    errors += validate_ret(self)
-    return errors
-
-  def parse_comment(self) -> dict:
-    ret_type, ret_desc = parse_ret(self)
-    return {
-        'name': self.name,
-        'desc': parse_desc(self),
-        'args': parse_args(self),
-        'return_type': ret_type,
-        'return_desc': ret_desc
-    }
-
-
-# Stores documentation for create_view_function with comment split into
-# segments.
-class ViewFunctionDocs:
-
-  def __init__(
-      self,
-      path: str,
-      data_from_sql: str,
-      module: str,
-      name: str,
-      desc: CommentLines,
-      args: CommentLines,
-      columns: CommentLines,
-  ):
-    self.path = path
-    self.data_from_sql = data_from_sql
-    self.module = module
-    self.name = name
-    self.desc = desc
-    self.args = args
-    self.columns = columns
-
-  # Contructs new ViewFunctionDocs from whole comment, by splitting it on typed
-  # lines. Returns None for improperly structured schemas.
-  @staticmethod
-  def create_from_comment(path: str, comment: CommentLines, module: str,
-                          matches: Tuple) -> Tuple['ViewFunctionDocs', Errors]:
-    name, args, columns, sql = matches
-
-    # Ignore internal functions.
-    if re.match(r"^INTERNAL_.*", name):
-      return None, []
-
-    errors = validate_name(name, module, upper=True)
-    args_dict, parse_errors = parse_args_str(args)
-    errors += parse_errors
-    has_desc, start_args, start_cols = False, None, None
-
-    # Splits code into segments by finding beginning of args and cols segments.
-    for i, line in enumerate(comment):
-      # Ignore only '--' line.
-      if line == "--":
-        continue
-
-      m = re.match(Pattern['typed_line'], line)
-
-      # Ignore untyped lines
-      if not m:
-        if not start_args:
-          has_desc = True
-        continue
-
-      line_type = m.group(1)
-      if line_type == "arg" and not start_args:
-        start_args = i
-        continue
-
-      if line_type == "column" and not start_cols:
-        start_cols = i
-        continue
-
-    if not has_desc:
-      errors.append(f"No description for '{name}' in {path}'\n")
-      return None, errors
-
-    if not start_cols or (args_dict and not start_args):
-      errors.append(f"Function requires 'arg' and 'column' comments.\n"
-                    f"'{name}' in {path}\n")
-      return None, errors
-
-    if not args_dict:
-      start_args = start_cols
-
-    cols_dict, parse_errors = parse_args_str(columns)
-    errors += parse_errors
-
-    data_from_sql = dict(name=name, args=args_dict, columns=cols_dict, sql=sql)
-    return (
-        ViewFunctionDocs(
-            path,
-            data_from_sql,
-            module,
-            name,
-            comment[:start_args],
-            comment[start_args:start_cols] if args_dict else None,
-            comment[start_cols:],
-        ),
-        errors,
-    )
-
-  def check_comment(self) -> Errors:
-    errors = validate_args(self)
-    errors += validate_columns(self, use_data_from_sql=True)
-    return errors
-
-  def parse_comment(self) -> dict:
-    return {
-        'name': self.name,
-        'desc': parse_desc(self),
-        'args': parse_args(self),
-        'cols': parse_columns(self)
-    }
-
-
-# Reads the provided SQL and, if possible, generates a dictionary with data
-# from documentation together with errors from validation of the schema.
-def parse_file_to_dict(path: str, sql: str) -> Tuple[Dict[str, any], Errors]:
-  if sys.platform.startswith('win'):
-    path = path.replace("\\", "/")
-
-  # Get module name
-  module_name = path.split("/stdlib/")[-1].split("/")[0]
-
-  imports, import_errors = parse_typed_docs(path, module_name, sql,
-                                            Pattern['create_table_view'],
-                                            TableViewDocs)
-  functions, function_errors = parse_typed_docs(path, module_name, sql,
-                                                Pattern['create_function'],
-                                                FunctionDocs)
-  view_functions, view_function_errors = parse_typed_docs(
-      path, module_name, sql, Pattern['create_view_function'], ViewFunctionDocs)
-
-  errors = import_errors + function_errors + view_function_errors
-
-  if errors:
-    sys.stderr.write("\n\n".join(errors))
-
-  return ({
-      'imports': [imp.parse_comment() for imp in imports if imp],
-      'functions': [fun.parse_comment() for fun in functions if fun],
-      'view_functions': [
-          view_fun.parse_comment() for view_fun in view_functions if view_fun
-      ]
-  }, errors)
diff --git a/python/generators/stdlib_docs/types.py b/python/generators/stdlib_docs/types.py
new file mode 100644
index 0000000..a9baad3
--- /dev/null
+++ b/python/generators/stdlib_docs/types.py
@@ -0,0 +1,21 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from enum import Enum
+
+
+class ObjKind(str, Enum):
+  table_view = 'table_view'
+  function = 'function'
+  view_function = 'view_function'
diff --git a/python/generators/stdlib_docs/utils.py b/python/generators/stdlib_docs/utils.py
index d23b414..eeccc01 100644
--- a/python/generators/stdlib_docs/utils.py
+++ b/python/generators/stdlib_docs/utils.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python3
 # Copyright (C) 2022 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,116 +12,82 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
 import re
-from typing import List, Tuple
+from typing import Dict, List
 
-Errors = List[str]
-CommentLines = List[str]
+from python.generators.stdlib_docs.types import ObjKind
 
-LOWER_NAME = r'[a-z_\d]*'
-UPPER_NAME = r'[A-Z_\d]*'
-ANY_WORDS = r'[A-Za-z_\d, \n]*'
-TYPE = r'[A-Z]*'
+LOWER_NAME = r'[a-z_\d]+'
+UPPER_NAME = r'[A-Z_\d]+'
+ANY_WORDS = r'[^\s].*'
+TYPE = r'[A-Z]+'
 SQL = r'[\s\S]*?'
 
-Pattern = {
-    'create_table_view': (
-        # Match create table/view and catch type
-        r'CREATE (?:VIRTUAL )?(TABLE|VIEW)?(?:IF NOT EXISTS)?\s*'
-        # Catch the name
-        fr'({LOWER_NAME})\s*(?:AS|USING)?.*'),
-    'create_function': (
-        r"SELECT\s*CREATE_FUNCTION\(\s*"
-        # Function name: we are matching everything [A-Z]* between ' and ).
-        fr"'\s*({UPPER_NAME})\s*\("
-        # Args: anything before closing bracket with '.
-        fr"({ANY_WORDS})\)',\s*"
-        # Type: [A-Z]* between two '.
-        fr"'({TYPE})',\s*"
-        # Sql: Anything between ' and ');. We are catching \'.
-        fr"'({SQL})'\s*\);"),
-    'create_view_function': (
-        r"SELECT\s*CREATE_VIEW_FUNCTION\(\s*"
-        # Function name: we are matching everything [A-Z]* between ' and ).
-        fr"'({UPPER_NAME})\s*\("
-        # Args: anything before closing bracket with '.
-        fr"({ANY_WORDS})\)',\s*"
-        # Return columns: anything between two '.
-        fr"'\s*({ANY_WORDS})',\s*"
-        # Sql: Anything between ' and ');. We are catching \'.
-        fr"'({SQL})'\s*\);"),
-    'column': fr'^-- @column\s*({LOWER_NAME})\s*({ANY_WORDS})',
-    'arg_str': fr"\s*({LOWER_NAME})\s*({TYPE})\s*",
-    'args': fr'^-- @arg\s*({LOWER_NAME})\s*({TYPE})\s*(.*)',
-    'return_arg': fr"^-- @ret ({TYPE})\s*(.*)",
-    'typed_line': fr'^-- @([a-z]*)'
+CREATE_TABLE_VIEW_PATTERN = (
+    # Match create table/view and catch type
+    r'CREATE (?:VIRTUAL )?(TABLE|VIEW)?(?:IF NOT EXISTS)?\s*'
+    # Catch the name
+    fr'({LOWER_NAME})\s*(?:AS|USING)?.*')
+
+CREATE_FUNCTION_PATTERN = (
+    r"SELECT\s*CREATE_FUNCTION\(\s*"
+    # Function name: we are matching everything [A-Z]* between ' and ).
+    fr"'\s*({UPPER_NAME})\s*\("
+    # Args: anything before closing bracket with '.
+    fr"({ANY_WORDS})\)',\s*"
+    # Type: [A-Z]* between two '.
+    fr"'({TYPE})',\s*"
+    # Sql: Anything between ' and ');. We are catching \'.
+    fr"'({SQL})'\s*\);")
+
+CREATE_VIEW_FUNCTION_PATTERN = (
+    r"SELECT\s*CREATE_VIEW_FUNCTION\(\s*"
+    # Function name: we are matching everything [A-Z]* between ' and ).
+    fr"'({UPPER_NAME})\s*\("
+    # Args: anything before closing bracket with '.
+    fr"({ANY_WORDS})\)',\s*"
+    # Return columns: anything between two '.
+    fr"'\s*({ANY_WORDS})',\s*"
+    # Sql: Anything between ' and ');. We are catching \'.
+    fr"'({SQL})'\s*\);")
+
+PATTERN_BY_KIND = {
+    ObjKind.table_view: CREATE_TABLE_VIEW_PATTERN,
+    ObjKind.function: CREATE_FUNCTION_PATTERN,
+    ObjKind.view_function: CREATE_VIEW_FUNCTION_PATTERN,
 }
 
+COLUMN_ANNOTATION_PATTERN = fr'^\s*({LOWER_NAME})\s*({ANY_WORDS})'
 
-def fetch_comment(lines_reversed: CommentLines) -> CommentLines:
-  comment_reversed = []
-  for line in lines_reversed:
+NAME_AND_TYPE_PATTERN = fr'\s*({LOWER_NAME})\s+({TYPE})\s*'
+
+ARG_ANNOTATION_PATTERN = fr'\s*{NAME_AND_TYPE_PATTERN}\s+({ANY_WORDS})'
+
+FUNCTION_RETURN_PATTERN = fr'^\s*({TYPE})\s+({ANY_WORDS})'
+
+
+# Given a list of lines in a text and the line number, scans backwards to find
+# all the comments.
+def extract_comment(lines: List[str], line_number: int) -> List[str]:
+  comments = []
+  for line in lines[line_number - 1::-1]:
     # Break on empty line, as that suggests it is no longer a part of
     # this comment.
     if not line or not line.startswith('--'):
       break
+    comments.append(line)
 
-    # The only  option left is a description, but it has to be after
-    # schema columns.
-    comment_reversed.append(line)
-
-  comment_reversed.reverse()
-  return comment_reversed
+  # Reverse as the above was reversed
+  comments.reverse()
+  return comments
 
 
-def match_pattern(pattern: str, file_str: str) -> dict:
-  objects = {}
+# Given a regex pattern and a string to match against, returns all the
+# matching positions. Specifically, it returns a dictionary from the line
+# number of the match to the regex match object.
+def match_pattern(pattern: str, file_str: str) -> Dict[int, re.Match]:
+  line_number_to_matches = {}
   for match in re.finditer(pattern, file_str):
     line_id = file_str[:match.start()].count('\n')
-    objects[line_id] = match.groups()
-  return dict(sorted(objects.items()))
-
-
-# Whether the name starts with module_name.
-def validate_name(name: str, module: str, upper: bool = False) -> Errors:
-  module_pattern = f"^{module}_.*"
-  if upper:
-    module_pattern = module_pattern.upper()
-  starts_with_module_name = re.match(module_pattern, name)
-  if module == "common":
-    if starts_with_module_name:
-      return [(f"Invalid name in module {name}. "
-               f"In module 'common' the name shouldn't "
-               f"start with '{module_pattern}'.\n")]
-  else:
-    if not starts_with_module_name:
-      return [(f"Invalid name in module {name}. "
-               f"Name has to begin with '{module_pattern}'.\n")]
-  return []
-
-
-# Parses string with multiple arguments with type separated by comma into dict.
-def parse_args_str(args_str: str) -> Tuple[dict, Errors]:
-  if not args_str.strip():
-    return None, []
-
-  errors = []
-  args = {}
-  for arg_str in args_str.split(","):
-    m = re.match(Pattern['arg_str'], arg_str)
-    if m is None:
-      errors.append(f"Wrong arguments formatting for '{arg_str}'\n")
-      continue
-    args[m.group(1)] = m.group(2)
-  return args, errors
-
-
-def get_text(line: str, no_break_line: bool = True) -> str:
-  line = line.lstrip('--').strip()
-  if not line:
-    return '' if no_break_line else '\n'
-  return line
+    line_number_to_matches[line_id] = match.groups()
+  return line_number_to_matches
diff --git a/python/generators/stdlib_docs/validate.py b/python/generators/stdlib_docs/validate.py
deleted file mode 100644
index fb419e2..0000000
--- a/python/generators/stdlib_docs/validate.py
+++ /dev/null
@@ -1,175 +0,0 @@
-#!/usr/bin/env python3
-# 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 re
-from typing import Union, List
-
-from python.generators.stdlib_docs import stdlib
-from python.generators.stdlib_docs.utils import Pattern, Errors
-
-
-# Whether the only typed comment in provided comment segment is of type
-# `comment_type`.
-def validate_typed_comment(
-    comment_segment: str,
-    comment_type: str,
-    docs: 'stdlib.AnyDocs',
-) -> Errors:
-  for line in comment_segment:
-    # Ignore only '--' line.
-    if line == "--":
-      continue
-
-    m = re.match(Pattern['typed_line'], line)
-
-    # Ignore untyped lines
-    if not m:
-      continue
-
-    line_type = m.group(1)
-
-    if line_type != comment_type:
-      return [(
-          f"Wrong comment type. Expected '{comment_type}', got '{line_type}'.\n"
-          f"'{docs.name}' in {docs.path}:\n'{line}'\n")]
-  return []
-
-
-# Whether comment segment about columns contain proper schema. Can be matched
-# against parsed SQL data by setting `use_data_from_sql`.
-def validate_columns(
-    docs: Union['stdlib.TableViewDocs', 'stdlib.ViewFunctionDocs'],
-    use_data_from_sql=False) -> Errors:
-  errors = validate_typed_comment(docs.columns, "column", docs)
-
-  if errors:
-    return errors
-
-  if use_data_from_sql:
-    cols_from_sql = docs.data_from_sql["columns"]
-
-  for line in docs.columns:
-    # Ignore only '--' line.
-    if line == "--" or not line.startswith("-- @column"):
-      continue
-
-    # Look for '-- @column' line as a column description
-    m = re.match(Pattern['column'], line)
-    if not m:
-      errors.append(f"Wrong column description.\n"
-                    f"'{docs.name}' in {docs.path}:\n'{line}'\n")
-      continue
-
-    if not m.group(2).strip():
-      errors.append(f"No description for column '{m.group(1)}'.\n"
-                    f"'{docs.name}' in {docs.path}:\n'{line}'\n")
-      continue
-
-    if not use_data_from_sql:
-      return errors
-
-    col_name = m.group(1)
-    if col_name not in cols_from_sql:
-      errors.append(f"There is no argument '{col_name}' specified in code.\n"
-                    f"'{docs.name}' in {docs.path}:\n'{line}'\n")
-      continue
-
-    cols_from_sql.pop(col_name)
-
-  if not use_data_from_sql:
-    errors.append(f"Missing columns for {docs.name}\n{docs.path}\n")
-    return errors
-
-  if not cols_from_sql:
-    return errors
-
-  errors.append(
-      f"Missing documentation of columns: {list(cols_from_sql.keys())}.\n"
-      f"'{docs.name}' in {docs.path}:\n")
-  return errors
-
-
-# Whether comment segment about columns contain proper schema. Matches against
-# parsed SQL data.
-def validate_args(docs: Union['stdlib.FunctionDocs', 'stdlib.ViewFunctionDocs']
-                 ) -> Errors:
-  if not docs.args:
-    return []
-
-  errors = validate_typed_comment(docs.args, "arg", docs)
-
-  if errors:
-    return errors
-
-  args_from_sql = docs.data_from_sql["args"]
-  for line in docs.args:
-    # Ignore only '--' line.
-    if line == "--" or not line.startswith("-- @"):
-      continue
-
-    m = re.match(Pattern['args'], line)
-    if m is None:
-      errors.append("The arg docs formatting is wrong. It should be:\n"
-                    "-- @arg [a-z_]* [A-Z]* {desc}\n"
-                    f"'{docs.name}' in {docs.path}:\n'{line}'\n")
-      return errors
-
-    arg_name, arg_type = m.group(1), m.group(2)
-    if arg_name not in args_from_sql:
-      errors.append(f"There is not argument '{arg_name}' specified in code.\n"
-                    f"'{docs.name}' in {docs.path}:\n'{line}'\n")
-      continue
-
-    arg_type_from_sql = args_from_sql.pop(arg_name)
-    if arg_type != arg_type_from_sql:
-      errors.append(f"In the code, the type of '{arg_name}' is "
-                    f"'{arg_type_from_sql}', but according to the docs "
-                    f"it is '{arg_type}'.\n"
-                    f"'{docs.name}' in {docs.path}:\n'{line}'\n")
-
-  if not args_from_sql:
-    return errors
-
-  errors.append(
-      f"Missing documentation of args: {list(args_from_sql.keys())}.\n"
-      f"'{docs.name}' in {docs.path}\n")
-  return errors
-
-
-# Whether comment segment about return contain proper schema. Matches against
-# parsed SQL data.
-def validate_ret(docs: "stdlib.FunctionDocs") -> Errors:
-  errors = validate_typed_comment(docs.ret, "ret", docs)
-  if errors:
-    return errors
-
-  ret_type_from_sql = docs.data_from_sql["ret"]
-
-  for line in docs.ret:
-    # Ignore only '--' line.
-    if line == "--" or not line.startswith("-- @ret"):
-      continue
-
-    m = re.match(Pattern['return_arg'], line)
-    if m is None:
-      return [("The return docs formatting is wrong. It should be:\n"
-               "-- @ret [A-Z]* {desc}\n"
-               f"'{docs.name}' in {docs.path}:\n'{line}'\n")]
-    docs_ret_type = m.group(1)
-    if ret_type_from_sql != docs_ret_type:
-      return [(f"The return type in docs is '{docs_ret_type}', "
-               f"but it is {ret_type_from_sql} in code.\n"
-               f"'{docs.name}' in {docs.path}:\n'{line}'\n")]
-    return []
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index ffd39d1..536dff9 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
index 20b87cf..2e0a2c6 100644
--- a/python/perfetto/trace_processor/trace_processor.descriptor
+++ b/python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/python/run_tests.py b/python/run_tests.py
index 245b7f6..b0a03c9 100755
--- a/python/run_tests.py
+++ b/python/run_tests.py
@@ -24,6 +24,7 @@
 from test import stdlib_unittest
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+sys.path.append(os.path.join(ROOT_DIR))
 
 
 def main():
diff --git a/python/setup.py b/python/setup.py
index db49cac..a484eda 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -8,7 +8,7 @@
     ],
     package_data={'perfetto.trace_processor': ['*.descriptor']},
     include_package_data=True,
-    version='0.6.0',
+    version='0.7.0',
     license='apache-2.0',
     description='Python API for Perfetto\'s Trace Processor',
     author='Perfetto',
diff --git a/python/test/stdlib_unittest.py b/python/test/stdlib_unittest.py
index 3d64be7..1f609d0 100644
--- a/python/test/stdlib_unittest.py
+++ b/python/test/stdlib_unittest.py
@@ -12,15 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import unittest
 import os
 import sys
+import unittest
 
 ROOT_DIR = os.path.dirname(
     os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 sys.path.append(os.path.join(ROOT_DIR))
 
-from generators.stdlib_docs.stdlib import FunctionDocs, ViewFunctionDocs, TableViewDocs
+from python.generators.stdlib_docs.parse import parse_file_to_dict
 
 DESC = """--
 -- First line.
@@ -34,14 +34,12 @@
 
 ARGS_STR = """--
 -- @arg utid INT              Utid of thread.
--- @arg name STRING           String name.
-"""
+-- @arg name STRING           String name."""
 
 ARGS_SQL_STR = "utid INT, name STRING"
 
 RET_STR = """--
--- @ret BOOL                  Exists.
-"""
+-- @ret BOOL                  Exists."""
 
 RET_SQL_STR = "BOOL"
 
@@ -50,162 +48,236 @@
 
 class TestStdlib(unittest.TestCase):
 
-  # Valid schemas
-
   def test_valid_table(self):
-    valid_table_comment = f"{DESC}\n{COLS_STR}".split('\n')
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+{COLS_STR}
+CREATE TABLE foo_table AS
+{SQL_STR};
+    '''.strip())
+    assert isinstance(res, dict)
 
-    docs, create_errors = TableViewDocs.create_from_comment(
-        "", valid_table_comment, 'common', ('table', 'tab_name', 'to_ignore'))
-    self.assertFalse(create_errors)
-
-    validation_errors = docs.check_comment()
-    self.assertFalse(validation_errors)
+    table = res['imports'][0]
+    self.assertEqual(table['name'], 'foo_table')
+    self.assertEqual(table['desc'], 'First line. Second line.')
+    self.assertEqual(table['type'], 'TABLE')
+    self.assertEqual(table['cols'], {
+        'slice_id': 'Id of slice.',
+        'slice_name': 'Name of slice.'
+    })
 
   def test_valid_function(self):
-    valid_function = f"{DESC}\n{ARGS_STR}\n{RET_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, RET_SQL_STR, SQL_STR)
-    docs, create_errors = FunctionDocs.create_from_comment(
-        "", valid_function, 'common', valid_regex_matches)
-    self.assertFalse(create_errors)
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+{ARGS_STR}
+{RET_STR}
+SELECT CREATE_FUNCTION(
+  'FOO_FN({ARGS_SQL_STR})',
+  '{RET_SQL_STR}',
+  '{SQL_STR}'
+);
+    '''.strip())
+    assert isinstance(res, dict)
 
-    validation_errors = docs.check_comment()
-    self.assertFalse(validation_errors)
+    fn = res['functions'][0]
+    self.assertEqual(fn['name'], 'FOO_FN')
+    self.assertEqual(fn['desc'], 'First line. Second line.')
+    self.assertEqual(
+        fn['args'], {
+            'utid': {
+                'type': 'INT',
+                'desc': 'Utid of thread.',
+            },
+            'name': {
+                'type': 'STRING',
+                'desc': 'String name.',
+            },
+        })
+    self.assertEqual(fn['return_type'], 'BOOL')
+    self.assertEqual(fn['return_desc'], 'Exists.')
 
   def test_valid_view_function(self):
-    valid_view_function = f"{DESC}\n{ARGS_STR}\n{COLS_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, COLS_SQL_STR, SQL_STR)
-    docs, create_errors = ViewFunctionDocs.create_from_comment(
-        "", valid_view_function, 'common', valid_regex_matches)
-    self.assertFalse(create_errors)
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+{ARGS_STR}
+{COLS_STR}
+SELECT CREATE_VIEW_FUNCTION(
+  'FOO_VIEW_FN({ARGS_SQL_STR})',
+  '{COLS_SQL_STR}',
+  '{SQL_STR}'
+);
+    '''.strip())
+    assert isinstance(res, dict)
 
-    validation_errors = docs.check_comment()
-    self.assertFalse(validation_errors)
+    fn = res['view_functions'][0]
+    self.assertEqual(fn['name'], 'FOO_VIEW_FN')
+    self.assertEqual(fn['desc'], 'First line. Second line.')
+    self.assertEqual(
+        fn['args'], {
+            'utid': {
+                'type': 'INT',
+                'desc': 'Utid of thread.',
+            },
+            'name': {
+                'type': 'STRING',
+                'desc': 'String name.',
+            },
+        })
+    self.assertEqual(fn['cols'], {
+        'slice_id': 'Id of slice.',
+        'slice_name': 'Name of slice.'
+    })
 
-  # Missing modules in names
+  def test_missing_module_name(self):
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+{COLS_STR}
+CREATE TABLE bar_table AS
+{SQL_STR};
+    '''.strip())
+    assert isinstance(res, list)
 
-  def test_missing_module_in_table_name(self):
-    valid_table_comment = f"{DESC}\n{COLS_STR}".split('\n')
+  def test_common_does_not_include_module_name(self):
+    res = parse_file_to_dict(
+        'common/bar.sql', f'''
+{DESC}
+{COLS_STR}
+CREATE TABLE common_table AS
+{SQL_STR};
+    '''.strip())
+    assert isinstance(res, list)
 
-    _, create_errors = TableViewDocs.create_from_comment(
-        "", valid_table_comment, 'android', ('table', 'tab_name', 'to_ignore'))
-    self.assertTrue(create_errors)
+  def test_cols_typo(self):
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+--
+-- @column slice_id2          Foo.
+-- @column slice_name         Bar.
+CREATE TABLE bar_table AS
+{SQL_STR};
+    '''.strip())
+    assert isinstance(res, list)
 
-  def test_missing_module_in_function_name(self):
-    valid_function = f"{DESC}\n{ARGS_STR}\n{RET_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, RET_SQL_STR, SQL_STR)
-    _, create_errors = FunctionDocs.create_from_comment("", valid_function,
-                                                        'android',
-                                                        valid_regex_matches)
-    self.assertTrue(create_errors)
+  def test_cols_no_desc(self):
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+--
+-- @column slice_id
+-- @column slice_name         Bar.
+CREATE TABLE bar_table AS
+{SQL_STR};
+    '''.strip())
+    assert isinstance(res, list)
 
-  def test_missing_module_in_view_function_name(self):
-    valid_view_function = f"{DESC}\n{ARGS_STR}\n{COLS_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, COLS_SQL_STR, SQL_STR)
-    _, create_errors = ViewFunctionDocs.create_from_comment(
-        "", valid_view_function, 'android', valid_regex_matches)
-    self.assertTrue(create_errors)
+  def test_args_typo(self):
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+--
+-- @arg utid2 INT              Uint.
+-- @arg name STRING           String name.
+{RET_STR}
+SELECT CREATE_FUNCTION(
+  'FOO_FN({ARGS_SQL_STR})',
+  '{RET_SQL_STR}',
+  '{SQL_STR}'
+);
+    '''.strip())
+    assert isinstance(res, list)
 
-  # Missing part of schemas
+  def test_args_no_desc(self):
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+--
+-- @arg utid INT
+-- @arg name STRING           String name.
+{RET_STR}
+SELECT CREATE_FUNCTION(
+  'FOO_FN({ARGS_SQL_STR})',
+  '{RET_SQL_STR}',
+  '{SQL_STR}'
+);
+    '''.strip())
+    assert isinstance(res, list)
 
-  def test_missing_desc_in_table_name(self):
-    comment = f"{COLS_STR}".split('\n')
+  def test_ret_no_desc(self):
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+{ARGS_STR}
+--
+-- @ret BOOL
+SELECT CREATE_FUNCTION(
+  'FOO_FN({ARGS_SQL_STR})',
+  '{RET_SQL_STR}',
+  '{SQL_STR}'
+);
+    '''.strip())
+    assert isinstance(res, list)
 
-    _, create_errors = TableViewDocs.create_from_comment(
-        "", comment, 'common', ('table', 'tab_name', 'to_ignore'))
-    self.assertTrue(create_errors)
+  def test_multiline_desc(self):
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+-- This
+-- is
+--
+-- a
+--      very
+--
+-- long
+--
+-- description.
+{ARGS_STR}
+{RET_STR}
+SELECT CREATE_FUNCTION(
+  'FOO_FN({ARGS_SQL_STR})',
+  '{RET_SQL_STR}',
+  '{SQL_STR}'
+);
+    '''.strip())
+    assert isinstance(res, dict)
 
-  def test_missing_cols_in_table(self):
-    comment = f"{DESC}".split('\n')
+    fn = res['functions'][0]
+    self.assertEqual(fn['desc'], 'This is a very long description.')
 
-    _, create_errors = TableViewDocs.create_from_comment(
-        "", comment, 'common', ('table', 'tab_name', 'to_ignore'))
-    self.assertTrue(create_errors)
+  def test_multiline_arg_desc(self):
+    res = parse_file_to_dict(
+        'foo/bar.sql', f'''
+{DESC}
+--
+-- @arg utid INT              Uint
+-- spread
+--
+-- across lines.
+-- @arg name STRING            String name
+--                             which spans across multiple lines
+-- inconsistently.
+{RET_STR}
+SELECT CREATE_FUNCTION(
+  'FOO_FN({ARGS_SQL_STR})',
+  '{RET_SQL_STR}',
+  '{SQL_STR}'
+);
+    '''.strip())
+    assert isinstance(res, dict)
 
-  def test_missing_desc_in_function(self):
-    comment = f"{ARGS_STR}\n{RET_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, RET_SQL_STR, SQL_STR)
-    _, create_errors = FunctionDocs.create_from_comment("", comment, 'common',
-                                                        valid_regex_matches)
-    self.assertTrue(create_errors)
-
-  def test_missing_args_in_function(self):
-    comment = f"{DESC}\n{RET_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, RET_SQL_STR, SQL_STR)
-    _, create_errors = FunctionDocs.create_from_comment("", comment, 'common',
-                                                        valid_regex_matches)
-    self.assertTrue(create_errors)
-
-  def test_missing_ret_in_function(self):
-    comment = f"{DESC}\n{ARGS_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, RET_SQL_STR, SQL_STR)
-    _, create_errors = FunctionDocs.create_from_comment("", comment, 'common',
-                                                        valid_regex_matches)
-    self.assertTrue(create_errors)
-
-  def test_missing_desc_in_view_function(self):
-    comment = f"{ARGS_STR}\n{COLS_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, COLS_SQL_STR, SQL_STR)
-    _, create_errors = ViewFunctionDocs.create_from_comment(
-        "", comment, 'common', valid_regex_matches)
-    self.assertTrue(create_errors)
-
-  def test_missing_args_in_view_function(self):
-    comment = f"{DESC}\n{COLS_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, COLS_SQL_STR, SQL_STR)
-    _, create_errors = ViewFunctionDocs.create_from_comment(
-        "", comment, 'common', valid_regex_matches)
-    self.assertTrue(create_errors)
-
-  def test_missing_cols_in_view_function(self):
-    comment = f"{DESC}\n{ARGS_STR}".split('\n')
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, COLS_SQL_STR, SQL_STR)
-    _, create_errors = ViewFunctionDocs.create_from_comment(
-        "", comment, 'common', valid_regex_matches)
-    self.assertTrue(create_errors)
-
-  # Validate elements
-
-  def test_invalid_table_columns(self):
-    invalid_cols = "-- @column slice_id"
-    comment = f"{DESC}\n{invalid_cols}".split('\n')
-
-    docs, create_errors = TableViewDocs.create_from_comment(
-        "", comment, 'common', ('table', 'tab_name', 'to_ignore'))
-    self.assertFalse(create_errors)
-
-    validation_errors = docs.check_comment()
-    self.assertTrue(validation_errors)
-
-  def test_invalid_view_function_columns(self):
-    comment = f"{DESC}\n{ARGS_STR}\n{COLS_STR}".split('\n')
-    cols_sql_str = "slice_id INT"
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, cols_sql_str, SQL_STR)
-    docs, create_errors = ViewFunctionDocs.create_from_comment(
-        "", comment, 'common', valid_regex_matches)
-    self.assertFalse(create_errors)
-
-    validation_errors = docs.check_comment()
-    self.assertTrue(validation_errors)
-
-  def test_invalid_arguments(self):
-    valid_function = f"{DESC}\n{ARGS_STR}\n{RET_STR}".split('\n')
-    args_sql_str = "utid BOOL"
-    valid_regex_matches = ('fun_name', args_sql_str, RET_SQL_STR, SQL_STR)
-    docs, create_errors = FunctionDocs.create_from_comment(
-        "", valid_function, 'common', valid_regex_matches)
-    self.assertFalse(create_errors)
-
-    validation_errors = docs.check_comment()
-    self.assertTrue(validation_errors)
-
-  def test_invalid_ret(self):
-    valid_function = f"{DESC}\n{ARGS_STR}\n{RET_STR}".split('\n')
-    ret_sql_str = "utid BOOL"
-    valid_regex_matches = ('fun_name', ARGS_SQL_STR, ret_sql_str, SQL_STR)
-    docs, create_errors = FunctionDocs.create_from_comment(
-        "", valid_function, 'common', valid_regex_matches)
-    self.assertFalse(create_errors)
-
-    validation_errors = docs.check_comment()
-    self.assertTrue(validation_errors)
+    fn = res['functions'][0]
+    self.assertEqual(
+        fn['args'], {
+            'utid': {
+                'type': 'INT',
+                'desc': 'Uint spread across lines.',
+            },
+            'name': {
+                'type': 'STRING',
+                'desc': 'String name which spans across multiple lines '
+                        'inconsistently.',
+            },
+        })
diff --git a/python/tools/record_android_trace.py b/python/tools/record_android_trace.py
index 9256bd8..1b1e659 100644
--- a/python/tools/record_android_trace.py
+++ b/python/tools/record_android_trace.py
@@ -64,12 +64,17 @@
 class HttpHandler(http.server.SimpleHTTPRequestHandler):
 
   def end_headers(self):
-    self.send_header('Access-Control-Allow-Origin', '*')
-    return super().end_headers()
+    self.send_header('Access-Control-Allow-Origin', self.server.allow_origin)
+    self.send_header('Cache-Control', 'no-cache')
+    super().end_headers()
 
   def do_GET(self):
-    self.server.last_request = self.path
-    return super().do_GET()
+    if self.path != '/' + self.server.expected_fname:
+      self.send_error(404, "File not found")
+      return
+
+    self.server.fname_get_completed = True
+    super().do_GET()
 
   def do_POST(self):
     self.send_error(404, "File not found")
@@ -95,9 +100,15 @@
   help = 'Output file or directory (default: %s)' % default_out_dir_str
   parser.add_argument('-o', '--out', default=default_out_dir, help=help)
 
-  help = 'Don\'t open in the browser'
+  help = 'Don\'t open or serve the trace'
   parser.add_argument('-n', '--no-open', action='store_true', help=help)
 
+  help = 'Don\'t open in browser, but still serve trace (good for remote use)'
+  parser.add_argument('--no-open-browser', action='store_true', help=help)
+
+  help = 'The web address used to open trace files'
+  parser.add_argument('--origin', default='https://ui.perfetto.dev', help=help)
+
   help = 'Force the use of the sideloaded binaries rather than system daemons'
   parser.add_argument('--sideload', action='store_true', help=help)
 
@@ -340,7 +351,8 @@
   if not args.no_open:
     prt('\n')
     prt('Opening the trace (%s) in the browser' % host_file)
-    open_trace_in_browser(host_file)
+    open_browser = not args.no_open_browser
+    open_trace_in_browser(host_file, open_browser, args.origin)
 
 
 def prt(msg, colors=ANSI.END):
@@ -368,17 +380,24 @@
     sys.exit(1)
 
 
-def open_trace_in_browser(path):
+def open_trace_in_browser(path, open_browser, origin):
   # We reuse the HTTP+RPC port because it's the only one allowed by the CSP.
   PORT = 9001
+  path = os.path.abspath(path)
   os.chdir(os.path.dirname(path))
   fname = os.path.basename(path)
   socketserver.TCPServer.allow_reuse_address = True
   with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd:
-    webbrowser.open_new_tab(
-        'https://ui.perfetto.dev/#!/?url=http://127.0.0.1:%d/%s' %
-        (PORT, fname))
-    while httpd.__dict__.get('last_request') != '/' + fname:
+    address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}'
+    if open_browser:
+      webbrowser.open_new_tab(address)
+    else:
+      print(f'Open URL in browser: {address}')
+
+    httpd.expected_fname = fname
+    httpd.fname_get_completed = None
+    httpd.allow_origin = origin
+    while httpd.fname_get_completed is None:
       httpd.handle_request()
 
 
diff --git a/src/android_internal/BUILD.gn b/src/android_internal/BUILD.gn
index 8435eab..a175063 100644
--- a/src/android_internal/BUILD.gn
+++ b/src/android_internal/BUILD.gn
@@ -32,6 +32,7 @@
       "health_hal.cc",
       "incident_service.cc",
       "power_stats.cc",
+      "statsd.cc",
       "statsd_logging.cc",
       "tracing_service_proxy.cc",
     ]
@@ -53,7 +54,7 @@
       "services",
       "tracingproxy",
       "utils",
-      "libstatspull",
+      "statspull",
     ]
 
     # This target should never depend on any other perfetto target to avoid ODR
diff --git a/src/android_internal/statsd.cc b/src/android_internal/statsd.cc
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/android_internal/statsd.cc
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/BUILD.gn b/src/base/BUILD.gn
index 8d48bf0..7dfce6b 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -199,6 +199,7 @@
     "scoped_file_unittest.cc",
     "small_vector_unittest.cc",
     "status_or_unittest.cc",
+    "status_unittest.cc",
     "string_splitter_unittest.cc",
     "string_utils_unittest.cc",
     "string_view_unittest.cc",
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/status.cc b/src/base/status.cc
index 30ccc47..d3c13e8 100644
--- a/src/base/status.cc
+++ b/src/base/status.cc
@@ -17,6 +17,7 @@
 #include "perfetto/base/status.h"
 
 #include <stdarg.h>
+#include <algorithm>
 
 namespace perfetto {
 namespace base {
@@ -31,5 +32,42 @@
   return status;
 }
 
+std::optional<std::string_view> Status::GetPayload(std::string_view type_url) {
+  if (ok()) {
+    return std::nullopt;
+  }
+  for (const auto& kv : payloads_) {
+    if (kv.type_url == type_url) {
+      return kv.payload;
+    }
+  }
+  return std::nullopt;
+}
+
+void Status::SetPayload(std::string_view type_url, std::string value) {
+  if (ok()) {
+    return;
+  }
+  for (auto& kv : payloads_) {
+    if (kv.type_url == type_url) {
+      kv.payload = value;
+      return;
+    }
+  }
+  payloads_.push_back(Payload{std::string(type_url), std::move(value)});
+}
+
+bool Status::ErasePayload(std::string_view type_url) {
+  if (ok()) {
+    return false;
+  }
+  auto it = std::remove_if(
+      payloads_.begin(), payloads_.end(),
+      [type_url](const Payload& p) { return p.type_url == type_url; });
+  bool erased = it != payloads_.end();
+  payloads_.erase(it, payloads_.end());
+  return erased;
+}
+
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/base/status_unittest.cc b/src/base/status_unittest.cc
new file mode 100644
index 0000000..df42b31
--- /dev/null
+++ b/src/base/status_unittest.cc
@@ -0,0 +1,67 @@
+/*
+ * 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 "perfetto/base/status.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace base {
+
+TEST(StatusTest, GetMissingPayload) {
+  base::Status status = base::ErrStatus("Error");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), std::nullopt);
+}
+
+TEST(StatusTest, SetThenGetPayload) {
+  base::Status status = base::ErrStatus("Error");
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), "payload_value");
+}
+
+TEST(StatusTest, SetEraseGetPayload) {
+  base::Status status = base::ErrStatus("Error");
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  ASSERT_TRUE(status.ErasePayload("test.foo.com/bar"));
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), std::nullopt);
+}
+
+TEST(StatusTest, SetOverride) {
+  base::Status status = base::ErrStatus("Error");
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  status.SetPayload("test.foo.com/bar", "other_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), "other_value");
+}
+
+TEST(StatusTest, SetGetOk) {
+  base::Status status = base::OkStatus();
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), std::nullopt);
+}
+
+TEST(StatusTest, SetMultipleAndDuplicate) {
+  base::Status status = base::ErrStatus("Error");
+  status.SetPayload("test.foo.com/bar", "payload_value");
+  status.SetPayload("test.foo.com/bar1", "1");
+  status.SetPayload("test.foo.com/bar2", "2");
+  status.SetPayload("test.foo.com/bar", "other_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar"), "other_value");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar1"), "1");
+  ASSERT_EQ(status.GetPayload("test.foo.com/bar2"), "2");
+}
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/base/utils.cc b/src/base/utils.cc
index 198daa9..82386fa 100644
--- a/src/base/utils.cc
+++ b/src/base/utils.cc
@@ -55,9 +55,16 @@
 #define PERFETTO_M_PURGE -101
 #endif  // M_PURGE
 
+#ifdef M_PURGE_ALL
+#define PERFETTO_M_PURGE_ALL M_PURGE_ALL
+#else
+// Only available in in-tree builds and on newer SDKs.
+#define PERFETTO_M_PURGE_ALL -104
+#endif  // M_PURGE
+
 namespace {
 extern "C" {
-using MalloptType = void (*)(int, int);
+using MalloptType = int (*)(int, int);
 }
 }  // namespace
 #endif  // OS_ANDROID
@@ -140,7 +147,9 @@
       reinterpret_cast<MalloptType>(dlsym(RTLD_DEFAULT, "mallopt"));
   if (!mallopt_fn)
     return;
-  mallopt_fn(PERFETTO_M_PURGE, 0);
+  if (mallopt_fn(PERFETTO_M_PURGE_ALL, 0) == 0) {
+    mallopt_fn(PERFETTO_M_PURGE, 0);
+  }
 #endif
 }
 
diff --git a/src/cloud_trace_processor/worker_impl.cc b/src/cloud_trace_processor/worker_impl.cc
index 2f124a0..6f11560 100644
--- a/src/cloud_trace_processor/worker_impl.cc
+++ b/src/cloud_trace_processor/worker_impl.cc
@@ -72,8 +72,6 @@
     // pools.
     auto tp = std::make_unique<TraceProcessorWrapper>(
         trace, thread_pool_, TraceProcessorWrapper::Statefulness::kStateless);
-    shard->tps.emplace_back(std::move(tp));
-
     auto load_trace_future =
         tp->LoadTrace(environment_->ReadFile(trace))
             .ContinueWith(
@@ -84,6 +82,7 @@
                   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));
 }
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/common/producer_support.h b/src/profiling/common/producer_support.h
index 6f54045..c1cd696 100644
--- a/src/profiling/common/producer_support.h
+++ b/src/profiling/common/producer_support.h
@@ -19,6 +19,7 @@
 
 #include <cinttypes>
 #include <string>
+#include <vector>
 
 #include "perfetto/tracing/core/forward_decls.h"
 
diff --git a/src/profiling/memory/unwinding.cc b/src/profiling/memory/unwinding.cc
index 3e03f5e..9263d0c 100644
--- a/src/profiling/memory/unwinding.cc
+++ b/src/profiling/memory/unwinding.cc
@@ -19,6 +19,9 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include <condition_variable>
+#include <mutex>
+
 #include <unwindstack/MachineArm.h>
 #include <unwindstack/MachineArm64.h>
 #include <unwindstack/MachineMips.h>
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/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/protozero/protoc_plugin/protozero_plugin.cc b/src/protozero/protoc_plugin/protozero_plugin.cc
index bcfbf28..55c3c9d 100644
--- a/src/protozero/protoc_plugin/protozero_plugin.cc
+++ b/src/protozero/protoc_plugin/protozero_plugin.cc
@@ -874,8 +874,9 @@
 
       for (int j = 0; j < nested_enum->value_count(); ++j) {
         const EnumValueDescriptor* value = nested_enum->value(j);
-        stub_h_->Print("static const $class$ $name$ = $class$::$name$;\n",
-                       "class", nested_enum->name(), "name", value->name());
+        stub_h_->Print(
+            "static inline const $class$ $name$ = $class$::$name$;\n", "class",
+            nested_enum->name(), "name", value->name());
       }
     }
 
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_merger/proto_file_serializer.cc b/src/tools/proto_merger/proto_file_serializer.cc
index dca5a07..c94a101 100644
--- a/src/tools/proto_merger/proto_file_serializer.cc
+++ b/src/tools/proto_merger/proto_file_serializer.cc
@@ -70,8 +70,11 @@
 
   std::string output;
   output += " [";
-  for (const auto& option : options) {
-    output += option.key + " = " + option.value;
+  size_t n = options.size();
+  for (size_t i = 0; i < n; i++) {
+    output += options[i].key + " = " + options[i].value;
+    if (i != n - 1)
+      output += ", ";
   }
   output += "]";
   return output;
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index 6f0660c..04814a6 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -268,6 +268,8 @@
     ":top_level_unittests",
     "containers:unittests",
     "db:unittests",
+    "db/overlays:unittests",
+    "db/storage:unittests",
     "importers/android_bugreport:unittests",
     "importers/common:unittests",
     "importers/ftrace:unittests",
diff --git a/src/trace_processor/containers/bit_vector.h b/src/trace_processor/containers/bit_vector.h
index c1cdc33..a9a5fbd 100644
--- a/src/trace_processor/containers/bit_vector.h
+++ b/src/trace_processor/containers/bit_vector.h
@@ -89,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_};
     }
 
diff --git a/src/trace_processor/containers/bit_vector_unittest.cc b/src/trace_processor/containers/bit_vector_unittest.cc
index 3db984c..dd8c951 100644
--- a/src/trace_processor/containers/bit_vector_unittest.cc
+++ b/src/trace_processor/containers/bit_vector_unittest.cc
@@ -572,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;
@@ -590,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.
@@ -607,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/db/BUILD.gn b/src/trace_processor/db/BUILD.gn
index bcde78f..1ef081a 100644
--- a/src/trace_processor/db/BUILD.gn
+++ b/src/trace_processor/db/BUILD.gn
@@ -20,19 +20,12 @@
     "base_id.h",
     "column.cc",
     "column.h",
-    "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.h",
-    "storage_variants.h",
     "table.cc",
     "table.h",
     "typed_column.h",
@@ -47,6 +40,8 @@
     "../../../include/perfetto/trace_processor",
     "../containers",
     "../util:glob",
+    "overlays",
+    "storage",
   ]
 }
 
@@ -59,7 +54,6 @@
   sources = [
     "column_storage_overlay_unittest.cc",
     "compare_unittest.cc",
-    "storage_unittest.cc",
     "view_unittest.cc",
   ]
   deps = [
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index f1adb96..66b273d 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -27,55 +27,12 @@
 #include "src/trace_processor/db/column_storage.h"
 #include "src/trace_processor/db/column_storage_overlay.h"
 #include "src/trace_processor/db/compare.h"
+#include "src/trace_processor/db/storage/types.h"
 #include "src/trace_processor/db/typed_column_internal.h"
 
 namespace perfetto {
 namespace trace_processor {
 
-// Represents the possible filter operations on a column.
-enum class FilterOp {
-  kEq,
-  kNe,
-  kGt,
-  kLt,
-  kGe,
-  kLe,
-  kIsNull,
-  kIsNotNull,
-  kGlob,
-};
-
-// Represents a constraint on a column.
-struct Constraint {
-  uint32_t col_idx;
-  FilterOp op;
-  SqlValue value;
-};
-
-// Represents an order by operation on a column.
-struct Order {
-  uint32_t col_idx;
-  bool desc;
-};
-
-// The enum type of the column.
-// Public only to stop GCC complaining about templates being defined in a
-// non-namespace scope (see ColumnTypeHelper below).
-enum class ColumnType {
-  // Standard primitive types.
-  kInt32,
-  kUint32,
-  kInt64,
-  kDouble,
-  kString,
-
-  // Types generated on the fly.
-  kId,
-
-  // Types which don't have any data backing them.
-  kDummy,
-};
-
 // Helper class for converting a type to a ColumnType.
 template <typename T>
 struct ColumnTypeHelper;
diff --git a/src/trace_processor/db/column_overlay.h b/src/trace_processor/db/column_overlay.h
deleted file mode 100644
index 7dc15f9..0000000
--- a/src/trace_processor/db/column_overlay.h
+++ /dev/null
@@ -1,49 +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.
- */
-
-#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&) = 0;
-
-  // Sorts (ascending) provided vector of indices based on storage.
-  virtual void Sort(std::vector<uint32_t>&) = 0;
-};
-}  // namespace column
-}  // namespace trace_processor
-}  // namespace perfetto
-
-#endif  // SRC_TRACE_PROCESSOR_DB_COLUMN_OVERLAY_H_
diff --git a/src/trace_processor/db/null_overlay.cc b/src/trace_processor/db/null_overlay.cc
new file mode 100644
index 0000000..71b38f4
--- /dev/null
+++ b/src/trace_processor/db/null_overlay.cc
@@ -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.
+ */
+
+#include "src/trace_processor/db/null_overlay.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+void NullOverlay::Filter(FilterOp op, SqlValue, 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 overlays
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/null_overlay.h b/src/trace_processor/db/null_overlay.h
index 52cf85c..dc38f38 100644
--- a/src/trace_processor/db/null_overlay.h
+++ b/src/trace_processor/db/null_overlay.h
@@ -17,31 +17,26 @@
 #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"
+#include "src/trace_processor/db/storage/storage.h"
 
 namespace perfetto {
 namespace trace_processor {
-namespace column {
+namespace overlays {
 
 // Overlay responsible for operations related to column nullability.
-class NullOverlay : public ColumnOverlay {
+class NullOverlay {
  public:
-  explicit NullOverlay(std::unique_ptr<ColumnOverlay>);
-  void Filter(FilterOp, SqlValue, RowMap&) override;
-  void Sort(std::vector<uint32_t>&) override;
+  explicit NullOverlay(const BitVector* null_bv) : null_bv_(null_bv) {}
+
+  void Filter(FilterOp, SqlValue, RowMap&) const;
+  void StableSort(uint32_t* rows, uint32_t rows_size) const;
 
  private:
-  std::unique_ptr<ColumnOverlay> inner_;
-
   // Vector of data nullability.
   const BitVector* null_bv_;
 };
 
-}  // namespace column
+}  // namespace overlays
 }  // namespace trace_processor
 }  // namespace perfetto
 
diff --git a/src/trace_processor/db/numeric_storage.cc b/src/trace_processor/db/numeric_storage.cc
deleted file mode 100644
index a1984a8..0000000
--- a/src/trace_processor/db/numeric_storage.cc
+++ /dev/null
@@ -1,230 +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.
- */
-
-#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 {
-// As we don't template those functions, we need to use std::visitor to type
-// `start`, hence this wrapping.
-inline uint32_t UpperBoundIndex(NumericValue val,
-                                const void* start,
-                                uint32_t num_elements) {
-  return std::visit(
-      [start, num_elements](auto val_data) {
-        using T = decltype(val_data);
-        const T* typed_start = static_cast<const T*>(start);
-        auto upper =
-            std::upper_bound(typed_start, typed_start + num_elements, 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.
-inline uint32_t LowerBoundIndex(NumericValue val,
-                                const void* start,
-                                uint32_t num_elements) {
-  return std::visit(
-      [start, num_elements](auto val_data) {
-        using T = decltype(val_data);
-        const T* typed_start = static_cast<const T*>(start);
-        auto upper =
-            std::lower_bound(typed_start, typed_start + num_elements, val_data);
-        return static_cast<uint32_t>(std::distance(typed_start, upper));
-      },
-      val);
-}
-
-// 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(std::vector<uint32_t>& out) const {
-  NumericValue val = *GetNumericTypeVariant(type_, SqlValue::Long(0));
-  std::visit(
-      [this, &out](auto val_data) {
-        using T = decltype(val_data);
-        const T* typed_start = static_cast<const T*>(data_);
-        std::stable_sort(out.begin(), out.end(),
-                         [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,
-                                 const void* start,
-                                 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(
-      [op, start, num_elements, &builder](auto num_val) {
-        using T = decltype(num_val);
-        auto* typed_start = static_cast<const T*>(start);
-        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,
-                                 const void* start,
-                                 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(
-      [op, start, num_elements, &builder](auto val) {
-        using T = decltype(val);
-        auto* typed_start = static_cast<const T*>(start);
-        TypedSlowPathComparison(val, op, typed_start, num_elements, builder);
-      },
-      *val);
-}
-
-void NumericStorage::CompareSorted(FilterOp op,
-                                   SqlValue sql_val,
-                                   const void* start,
-                                   uint32_t num_elements,
-                                   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, start, num_elements);
-      uint32_t end = UpperBoundIndex(*val, start, num_elements);
-      RowMap sec(beg, end);
-      rm.Intersect(sec);
-      return;
-    }
-    case FilterOp::kLe: {
-      uint32_t end = UpperBoundIndex(*val, start, num_elements);
-      RowMap sec(0, end);
-      rm.Intersect(sec);
-      return;
-    }
-    case FilterOp::kLt: {
-      uint32_t end = LowerBoundIndex(*val, start, num_elements);
-      RowMap sec(0, end);
-      rm.Intersect(sec);
-      return;
-    }
-    case FilterOp::kGe: {
-      uint32_t beg = LowerBoundIndex(*val, start, num_elements);
-      RowMap sec(beg, num_elements);
-      rm.Intersect(sec);
-      return;
-    }
-    case FilterOp::kGt: {
-      uint32_t beg = UpperBoundIndex(*val, start, num_elements);
-      RowMap sec(beg, num_elements);
-      rm.Intersect(sec);
-      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
deleted file mode 100644
index 7677f82..0000000
--- a/src/trace_processor/db/numeric_storage.h
+++ /dev/null
@@ -1,67 +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.
- */
-#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(const void* data, ColumnType type)
-      : type_(type), data_(data) {}
-
-  void StableSort(std::vector<uint32_t>&) const override;
-
-  void CompareFast(FilterOp op,
-                   SqlValue val,
-                   const void* start,
-                   uint32_t num_elements,
-                   BitVector::Builder& builder) const override;
-
-  // 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.
-  void CompareSlow(FilterOp op,
-                   SqlValue val,
-                   const void* start,
-                   uint32_t num_elements,
-                   BitVector::Builder& builder) const override;
-
-  // Compares sorted (asc) series of |num_elements| of data from |data_start| to
-  // comparator value. Should be used where possible.
-  void CompareSorted(FilterOp op,
-                     SqlValue val,
-                     const void* data_start,
-                     uint32_t num_elements,
-                     RowMap&) const override;
-
- private:
-  const ColumnType type_;
-  const void* data_;
-};
-
-}  // namespace column
-}  // namespace trace_processor
-}  // namespace perfetto
-#endif  // SRC_TRACE_PROCESSOR_DB_NUMERIC_STORAGE_H_
diff --git a/src/trace_processor/db/overlays/BUILD.gn b/src/trace_processor/db/overlays/BUILD.gn
new file mode 100644
index 0000000..c35891f
--- /dev/null
+++ b/src/trace_processor/db/overlays/BUILD.gn
@@ -0,0 +1,46 @@
+# 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_tp_tables.gni")
+import("../../../../gn/test.gni")
+
+source_set("overlays") {
+  sources = [
+    "null_overlay.cc",
+    "null_overlay.h",
+    "selector_overlay.cc",
+    "selector_overlay.h",
+    "storage_overlay.cc",
+    "storage_overlay.h",
+    "types.h",
+  ]
+  deps = [
+    "../../../../gn:default_deps",
+    "../../../base",
+    "../../containers",
+  ]
+}
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  sources = [
+    "null_overlay_unittest.cc",
+    "selector_overlay_unittest.cc",
+  ]
+  deps = [
+    ":overlays",
+    "../../../../gn:default_deps",
+    "../../../../gn:gtest_and_gmock",
+  ]
+}
diff --git a/src/trace_processor/db/overlays/null_overlay.cc b/src/trace_processor/db/overlays/null_overlay.cc
new file mode 100644
index 0000000..c5f5f66
--- /dev/null
+++ b/src/trace_processor/db/overlays/null_overlay.cc
@@ -0,0 +1,131 @@
+/*
+ * 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/overlays/null_overlay.h"
+#include "perfetto/ext/base/flat_hash_map.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+using Range = RowMap::Range;
+
+StorageRange NullOverlay::MapToStorageRange(TableRange t_range) const {
+  uint32_t start = non_null_->CountSetBits(t_range.range.start);
+  uint32_t end = non_null_->CountSetBits(t_range.range.end);
+
+  return StorageRange({Range(start, end)});
+}
+
+TableBitVector NullOverlay::MapToTableBitVector(StorageBitVector s_bv) const {
+  BitVector res = non_null_->Copy();
+  res.UpdateSetBits(s_bv.bv);
+  return {std::move(res)};
+}
+
+BitVector NullOverlay::IsStorageLookupRequired(
+    OverlayOp op,
+    const TableIndexVector& t_iv) const {
+  PERFETTO_DCHECK(t_iv.indices.size() <= non_null_->size());
+
+  if (op != OverlayOp::kOther)
+    return BitVector();
+
+  BitVector in_storage(static_cast<uint32_t>(t_iv.indices.size()), false);
+
+  // For each index in TableIndexVector check whether this index is in storage.
+  for (uint32_t i = 0; i < t_iv.indices.size(); ++i) {
+    if (non_null_->IsSet(t_iv.indices[i]))
+      in_storage.Set(i);
+  }
+
+  return in_storage;
+}
+
+StorageIndexVector NullOverlay::MapToStorageIndexVector(
+    TableIndexVector t_iv_with_idx_in_storage) const {
+  PERFETTO_DCHECK(t_iv_with_idx_in_storage.indices.size() <=
+                  non_null_->CountSetBits());
+
+  std::vector<uint32_t> storage_index_vector;
+  storage_index_vector.reserve(t_iv_with_idx_in_storage.indices.size());
+  for (auto t_idx : t_iv_with_idx_in_storage.indices) {
+    storage_index_vector.push_back(non_null_->CountSetBits(t_idx));
+  }
+
+  return StorageIndexVector({std::move(storage_index_vector)});
+}
+
+BitVector NullOverlay::IndexSearch(
+    OverlayOp op,
+    const TableIndexVector& t_iv_overlay_idx) const {
+  if (op == OverlayOp::kOther)
+    return BitVector();
+
+  BitVector res(static_cast<uint32_t>(t_iv_overlay_idx.indices.size()), false);
+  if (op == OverlayOp::kIsNull) {
+    for (uint32_t i = 0; i < res.size(); ++i) {
+      if (!non_null_->IsSet(t_iv_overlay_idx.indices[i]))
+        res.Set(i);
+    }
+    return res;
+  }
+
+  PERFETTO_DCHECK(op == OverlayOp::kIsNotNull);
+  for (uint32_t i = 0; i < res.size(); ++i) {
+    if (non_null_->IsSet(t_iv_overlay_idx.indices[i]))
+      res.Set(i);
+  }
+  return res;
+}
+
+CostEstimatePerRow NullOverlay::EstimateCostPerRow(OverlayOp op) const {
+  // TODO(b/283763282): Replace with benchmarked data.
+  CostEstimatePerRow res;
+
+  // Two |BitVector::CountSetBits| calls.
+  res.to_storage_range = 100;
+
+  // Cost of |BitVector::UpdateSetBits|
+  res.to_table_bit_vector = 100;
+
+  if (op == OverlayOp::kOther) {
+    // Cost of |BitVector::IsSet| and |BitVector::Set|
+    res.is_storage_search_required = 10;
+
+    // Cost of iterating all set bits and looping the index vector divided by
+    // number of indices.
+    res.map_to_storage_index_vector = 100;
+
+    // Won't be called.
+    res.index_search = 0;
+  } else {
+    // Cost of creating trivial BitVector.
+    res.is_storage_search_required = 0;
+
+    // Won't be called
+    res.map_to_storage_index_vector = 0;
+
+    // Cost of calling |BitVector::IsSet|
+    res.index_search = 10;
+  }
+
+  return res;
+}
+
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/null_overlay.h b/src/trace_processor/db/overlays/null_overlay.h
new file mode 100644
index 0000000..93da2c0
--- /dev/null
+++ b/src/trace_processor/db/overlays/null_overlay.h
@@ -0,0 +1,54 @@
+/*
+ * 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_OVERLAYS_NULL_OVERLAY_H_
+#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_NULL_OVERLAY_H_
+
+#include "src/trace_processor/db/overlays/storage_overlay.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+// Introduces the layer of nullability - spreads out the storage with nulls
+// using BitVector.
+class NullOverlay : public StorageOverlay {
+ public:
+  explicit NullOverlay(BitVector* null) : non_null_(std::move(null)) {}
+
+  StorageRange MapToStorageRange(TableRange) const override;
+
+  TableBitVector MapToTableBitVector(StorageBitVector) const override;
+
+  BitVector IsStorageLookupRequired(OverlayOp,
+                                    const TableIndexVector&) const override;
+
+  StorageIndexVector MapToStorageIndexVector(TableIndexVector) const override;
+
+  BitVector IndexSearch(OverlayOp, const TableIndexVector&) const override;
+
+  CostEstimatePerRow EstimateCostPerRow(OverlayOp) const override;
+
+ private:
+  // Non null data in the overlay.
+  BitVector* non_null_;
+};
+
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_NULL_OVERLAY_H_
diff --git a/src/trace_processor/db/overlays/null_overlay_unittest.cc b/src/trace_processor/db/overlays/null_overlay_unittest.cc
new file mode 100644
index 0000000..6f80eba
--- /dev/null
+++ b/src/trace_processor/db/overlays/null_overlay_unittest.cc
@@ -0,0 +1,129 @@
+/*
+ * 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/overlays/null_overlay.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+namespace {
+
+TEST(NullOverlay, MapToStorageRangeOutsideBoundary) {
+  BitVector bv{0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+  NullOverlay overlay(&bv);
+  StorageRange r = overlay.MapToStorageRange({RowMap::Range(1, 6)});
+
+  ASSERT_EQ(r.range.start, 0u);
+  ASSERT_EQ(r.range.end, 2u);
+}
+
+TEST(NullOverlay, MapToStorageRangeOnBoundary) {
+  BitVector bv{0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0};
+  NullOverlay overlay(&bv);
+  StorageRange r = overlay.MapToStorageRange({RowMap::Range(3, 8)});
+
+  ASSERT_EQ(r.range.start, 1u);
+  ASSERT_EQ(r.range.end, 4u);
+}
+
+TEST(NullOverlay, MapToTableBitVector) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  BitVector storage_bv{0, 1, 0, 1};
+  TableBitVector table_bv =
+      overlay.MapToTableBitVector({std::move(storage_bv)});
+
+  ASSERT_EQ(table_bv.bv.CountSetBits(), 2u);
+  ASSERT_TRUE(table_bv.bv.IsSet(2));
+  ASSERT_TRUE(table_bv.bv.IsSet(6));
+}
+
+TEST(NullOverlay, IsStorageLookupRequiredNullOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 2, 4, 6};
+  BitVector lookup_bv =
+      overlay.IsStorageLookupRequired(OverlayOp::kIsNull, {table_idx});
+
+  ASSERT_EQ(lookup_bv.size(), 0u);
+}
+
+TEST(NullOverlay, IsStorageLookupRequiredOtherOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 2, 4, 6};
+  BitVector lookup_bv =
+      overlay.IsStorageLookupRequired(OverlayOp::kOther, {table_idx});
+
+  ASSERT_EQ(lookup_bv.size(), 4u);
+  ASSERT_EQ(lookup_bv.CountSetBits(), 2u);
+  ASSERT_TRUE(lookup_bv.IsSet(1));
+  ASSERT_TRUE(lookup_bv.IsSet(3));
+}
+
+TEST(NullOverlay, MapToStorageIndexVector) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{1, 5, 2};
+  StorageIndexVector storage_iv = overlay.MapToStorageIndexVector({table_idx});
+
+  std::vector<uint32_t> res{0, 2, 1};
+  ASSERT_EQ(storage_iv.indices, res);
+}
+
+TEST(NullOverlay, IndexSearchOtherOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 3, 4};
+  BitVector idx_search_bv = overlay.IndexSearch(OverlayOp::kOther, {table_idx});
+
+  ASSERT_EQ(idx_search_bv.size(), 0u);
+}
+
+TEST(NullOverlay, IndexSearchIsNullOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 3, 4};
+  BitVector idx_search_bv =
+      overlay.IndexSearch(OverlayOp::kIsNull, {table_idx});
+
+  ASSERT_EQ(idx_search_bv.size(), 3u);
+  ASSERT_EQ(idx_search_bv.CountSetBits(), 3u);
+}
+
+TEST(NullOverlay, IndexSearchIsNotNullOp) {
+  BitVector bv{0, 1, 1, 0, 0, 1, 1, 0};
+  NullOverlay overlay(&bv);
+
+  std::vector<uint32_t> table_idx{0, 3, 4};
+  BitVector idx_search_bv =
+      overlay.IndexSearch(OverlayOp::kIsNotNull, {table_idx});
+
+  ASSERT_EQ(idx_search_bv.size(), 3u);
+  ASSERT_EQ(idx_search_bv.CountSetBits(), 0u);
+}
+
+}  // namespace
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/selector_overlay.cc b/src/trace_processor/db/overlays/selector_overlay.cc
new file mode 100644
index 0000000..e501ddf
--- /dev/null
+++ b/src/trace_processor/db/overlays/selector_overlay.cc
@@ -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.
+ */
+
+#include "src/trace_processor/db/overlays/selector_overlay.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+using Range = RowMap::Range;
+
+StorageRange SelectorOverlay::MapToStorageRange(TableRange t_range) const {
+  // Table data is smaller than Storage, so we need to expand the data.
+  return StorageRange{
+      Range(selected_->IndexOfNthSet(t_range.range.start),
+            selected_->IndexOfNthSet(t_range.range.end - 1) + 1)};
+}
+
+TableBitVector SelectorOverlay::MapToTableBitVector(
+    StorageBitVector s_bv) const {
+  PERFETTO_DCHECK(s_bv.bv.size() == selected_->size());
+  BitVector res(selected_->CountSetBits());
+  // TODO(b/283763282): Implement this variation of |UpdateSetBits| in
+  // BitVector.
+  for (auto it = selected_->IterateSetBits(); it; it.Next()) {
+    if (s_bv.bv.IsSet(it.index()))
+      res.Set(it.ordinal());
+  }
+  return TableBitVector({std::move(res)});
+}
+
+BitVector SelectorOverlay::IsStorageLookupRequired(
+    OverlayOp,
+    const TableIndexVector& t_iv) const {
+  return BitVector(static_cast<uint32_t>(t_iv.indices.size()), true);
+}
+
+StorageIndexVector SelectorOverlay::MapToStorageIndexVector(
+    TableIndexVector t_iv) const {
+  // To go from TableIndexVector to StorageIndexVector we need to find index in
+  // |selector_| by looking only into set bits.
+  std::vector<uint32_t> s_iv;
+  s_iv.reserve(t_iv.indices.size());
+  for (auto t_idx : t_iv.indices) {
+    s_iv.push_back(selected_->IndexOfNthSet(t_idx));
+  }
+
+  return StorageIndexVector({std::move(s_iv)});
+}
+
+BitVector SelectorOverlay::IndexSearch(OverlayOp,
+                                       const TableIndexVector&) const {
+  // |t_iv| doesn't contain any values that are null in |selected_| as other
+  // overlays are not able to access them. This function should not be called.
+  PERFETTO_FATAL("Should not be called in SelectorOverlay.");
+}
+
+CostEstimatePerRow SelectorOverlay::EstimateCostPerRow(OverlayOp) const {
+  CostEstimatePerRow estimate;
+  // Cost of two |IndexOfNthSet|
+  estimate.to_storage_range = 20;
+  // Cost of iterating over all selected bits and calling |IsSet| each time (and
+  // |Set| if true)
+  estimate.to_table_bit_vector = 100;
+  // Cost of creating trivial vector of 1s
+  estimate.is_storage_search_required = 0;
+  // Cost of |IndexOfNthSet| for each row
+  estimate.map_to_storage_index_vector = 10;
+  // Shouldn't be called
+  estimate.index_search = 0;
+
+  return estimate;
+}
+
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/selector_overlay.h b/src/trace_processor/db/overlays/selector_overlay.h
new file mode 100644
index 0000000..70be983
--- /dev/null
+++ b/src/trace_processor/db/overlays/selector_overlay.h
@@ -0,0 +1,52 @@
+/*
+ * 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_OVERLAYS_SELECTOR_OVERLAY_H_
+#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_SELECTOR_OVERLAY_H_
+
+#include "src/trace_processor/db/overlays/storage_overlay.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+// Overlay responsible for selecting specific rows from Storage.
+class SelectorOverlay : public StorageOverlay {
+ public:
+  explicit SelectorOverlay(BitVector* selected) : selected_(selected) {}
+
+  StorageRange MapToStorageRange(TableRange) const override;
+
+  TableBitVector MapToTableBitVector(StorageBitVector) const override;
+
+  BitVector IsStorageLookupRequired(OverlayOp,
+                                    const TableIndexVector&) const override;
+
+  StorageIndexVector MapToStorageIndexVector(TableIndexVector) const override;
+
+  BitVector IndexSearch(OverlayOp, const TableIndexVector&) const override;
+
+  CostEstimatePerRow EstimateCostPerRow(OverlayOp) const override;
+
+ private:
+  BitVector* selected_;
+};
+
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_SELECTOR_OVERLAY_H_
diff --git a/src/trace_processor/db/overlays/selector_overlay_unittest.cc b/src/trace_processor/db/overlays/selector_overlay_unittest.cc
new file mode 100644
index 0000000..6e14597
--- /dev/null
+++ b/src/trace_processor/db/overlays/selector_overlay_unittest.cc
@@ -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.
+ */
+
+#include "src/trace_processor/db/overlays/selector_overlay.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+namespace {
+
+TEST(SelectorOverlay, MapToStorageRangeFirst) {
+  BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1};
+  SelectorOverlay overlay(&selector);
+  StorageRange r = overlay.MapToStorageRange({RowMap::Range(1, 4)});
+
+  ASSERT_EQ(r.range.start, 4u);
+  ASSERT_EQ(r.range.end, 8u);
+}
+
+TEST(SelectorOverlay, MapToStorageRangeSecond) {
+  BitVector selector{0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0};
+  SelectorOverlay overlay(&selector);
+  StorageRange r = overlay.MapToStorageRange({RowMap::Range(1, 3)});
+
+  ASSERT_EQ(r.range.start, 4u);
+  ASSERT_EQ(r.range.end, 7u);
+}
+
+TEST(SelectorOverlay, MapToTableBitVector) {
+  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
+  SelectorOverlay overlay(&selector);
+
+  BitVector storage_bv{1, 0, 1, 0, 1, 0, 1, 0};
+  TableBitVector table_bv =
+      overlay.MapToTableBitVector({std::move(storage_bv)});
+
+  ASSERT_EQ(table_bv.bv.size(), 4u);
+  ASSERT_EQ(table_bv.bv.CountSetBits(), 2u);
+  ASSERT_TRUE(table_bv.bv.IsSet(1));
+  ASSERT_TRUE(table_bv.bv.IsSet(3));
+}
+
+TEST(SelectorOverlay, IsStorageLookupRequired) {
+  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
+  SelectorOverlay overlay(&selector);
+
+  std::vector<uint32_t> table_idx{0, 1, 2};
+  BitVector lookup_bv =
+      overlay.IsStorageLookupRequired(OverlayOp::kIsNull, {table_idx});
+
+  ASSERT_EQ(lookup_bv.size(), 3u);
+}
+
+TEST(SelectorOverlay, MapToStorageIndexVector) {
+  BitVector selector{0, 1, 1, 0, 0, 1, 1, 0};
+  SelectorOverlay overlay(&selector);
+
+  std::vector<uint32_t> table_idx{1, 3, 2};
+  StorageIndexVector storage_iv = overlay.MapToStorageIndexVector({table_idx});
+
+  std::vector<uint32_t> res{2, 6, 5};
+  ASSERT_EQ(storage_iv.indices, res);
+}
+
+}  // namespace
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage.cc b/src/trace_processor/db/overlays/storage_overlay.cc
similarity index 82%
copy from src/trace_processor/db/storage.cc
copy to src/trace_processor/db/overlays/storage_overlay.cc
index 4799d04..56897f2 100644
--- a/src/trace_processor/db/storage.cc
+++ b/src/trace_processor/db/overlays/storage_overlay.cc
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/db/storage.h"
+#include "src/trace_processor/db/overlays/storage_overlay.h"
 
 namespace perfetto {
 namespace trace_processor {
-namespace column {
+namespace overlays {
 
-Storage::~Storage() = default;
+StorageOverlay::~StorageOverlay() = default;
 
-}  // namespace column
+}  // namespace overlays
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/overlays/storage_overlay.h b/src/trace_processor/db/overlays/storage_overlay.h
new file mode 100644
index 0000000..58dbf50
--- /dev/null
+++ b/src/trace_processor/db/overlays/storage_overlay.h
@@ -0,0 +1,89 @@
+/*
+ * 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_OVERLAYS_STORAGE_OVERLAY_H_
+#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_STORAGE_OVERLAY_H_
+
+#include "src/trace_processor/db/overlays/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+// Abstract class which is layered on top of Storage transforming how the
+// storage should be interpreted. The main purpose of this class is to be
+// responsible for for mapping between table indices and storage indices (i.e.
+// in both directions).
+//
+// Overlays are designed to be "layered" on top of each other (i.e. the mapping
+// algorithms compose). To make it easier to reason about this class, we
+// ignore any other overlays and assume we are mapping directly between table
+// indices and storage indices. i.e. even if "table indices" we are working with
+// come from another overlay, we still consider them as having come from the
+// table and vice versa for "storage indices".
+class StorageOverlay {
+ public:
+  // The core functions in this class work with input and output arguments which
+  // use the same data structure but have different semantics (i.e. input might
+  // be in terms of storage indices and output might be in terms of table
+  // indices).
+  //
+  // For this reason, we use the defined wrapper structs which "tag" the data
+  // structure with the semantics.
+
+  virtual ~StorageOverlay();
+
+  // Maps a range of indices in table space to an equivalent range of
+  // indices in the storage space.
+  virtual StorageRange MapToStorageRange(TableRange) const = 0;
+
+  // Maps a BitVector of indices in storage space to an equivalent range of
+  // indices in the table space.
+  virtual TableBitVector MapToTableBitVector(StorageBitVector) const = 0;
+
+  // Returns a BitVector where each boolean indicates if the corresponding index
+  // in |indices| needs to be mapped and searched in the storage or if the
+  // overlay can provide the answer without storage lookup.
+  virtual BitVector IsStorageLookupRequired(OverlayOp,
+                                            const TableIndexVector&) const = 0;
+
+  // Maps a vector of indices in the table space with an equivalent range
+  // of indices in the storage space.
+  //
+  // Note: callers must call |IsStorageSearchRequired| first and only call
+  // this method with indices where |IsStorageSearchRequired| returned true.
+  // Passing indices here which are not mappable is undefined behaviour.
+  virtual StorageIndexVector MapToStorageIndexVector(
+      TableIndexVector) const = 0;
+
+  // Given a vector of indices given in table space, returns whether the index
+  // matches the operation given by |op|.
+  //
+  // Note: callers must call |IsStorageSearchRequired| first and only call
+  // this method with indices where |IsStorageSearchRequired| returned false.
+  // Passing indices here which are not searchable is undefined behaviour.
+  virtual BitVector IndexSearch(OverlayOp, const TableIndexVector&) const = 0;
+
+  // Estimates the per-row costs of the methods of this class. Allows for
+  // deciding which algorithm to use to search/sort the storage.
+  virtual CostEstimatePerRow EstimateCostPerRow(OverlayOp) const = 0;
+};
+
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_STORAGE_OVERLAY_H_
diff --git a/src/trace_processor/db/overlays/types.h b/src/trace_processor/db/overlays/types.h
new file mode 100644
index 0000000..4e72a41
--- /dev/null
+++ b/src/trace_processor/db/overlays/types.h
@@ -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.
+ */
+#ifndef SRC_TRACE_PROCESSOR_DB_OVERLAYS_TYPES_H_
+#define SRC_TRACE_PROCESSOR_DB_OVERLAYS_TYPES_H_
+
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/row_map.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace overlays {
+
+// A range of indices in the table space.
+struct TableRange {
+  RowMap::Range range;
+};
+
+// A range of indices in the storage space.
+struct StorageRange {
+  RowMap::Range range;
+};
+
+// A BitVector with set bits corresponding to indices in the table space.
+struct TableBitVector {
+  BitVector bv;
+};
+
+// A BitVector with set bits corresponding to indices in the table space.
+struct StorageBitVector {
+  BitVector bv;
+};
+
+// Represents a vector of indices in the table space.
+struct TableIndexVector {
+  std::vector<uint32_t> indices;
+};
+
+// Represents a vector of indices in the storage space.
+struct StorageIndexVector {
+  std::vector<uint32_t> indices;
+};
+
+// A subset of FilterOp containing operations which can be handled by
+// overlays.
+enum class OverlayOp {
+  kIsNull,
+  kIsNotNull,
+  kOther,
+};
+
+// Contains estimates of the cost for each of method in this class per row.
+struct CostEstimatePerRow {
+  uint32_t to_storage_range;
+  uint32_t to_table_bit_vector;
+  uint32_t is_storage_search_required;
+  uint32_t map_to_storage_index_vector;
+  uint32_t index_search;
+};
+
+}  // namespace overlays
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_OVERLAYS_TYPES_H_
diff --git a/src/trace_processor/db/sorting_overlay.h b/src/trace_processor/db/sorting_overlay.h
deleted file mode 100644
index 86a5676..0000000
--- a/src/trace_processor/db/sorting_overlay.h
+++ /dev/null
@@ -1,47 +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.
- */
-
-#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&) override;
-  void Sort(std::vector<uint32_t>&) 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/db/storage.h b/src/trace_processor/db/storage.h
deleted file mode 100644
index dbf037d..0000000
--- a/src/trace_processor/db/storage.h
+++ /dev/null
@@ -1,66 +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.
- */
-#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(std::vector<uint32_t>&) 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,
-                           const void* start,
-                           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,
-                           const void* data_start,
-                           uint32_t compare_elements_count,
-                           BitVector::Builder&) const = 0;
-
-  // Compares sorted (asc) series of |num_elements| of data from |data_start| to
-  // comparator value. Should be used where possible.
-  virtual void CompareSorted(FilterOp op,
-                             SqlValue value,
-                             const void* data_start,
-                             uint32_t compare_elements_count,
-                             RowMap&) const = 0;
-};
-
-}  // namespace column
-}  // namespace trace_processor
-}  // namespace perfetto
-#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_H_
diff --git a/src/trace_processor/db/storage/BUILD.gn b/src/trace_processor/db/storage/BUILD.gn
new file mode 100644
index 0000000..81d3b5f
--- /dev/null
+++ b/src/trace_processor/db/storage/BUILD.gn
@@ -0,0 +1,40 @@
+# 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/test.gni")
+
+source_set("storage") {
+  sources = [
+    "numeric_storage.cc",
+    "numeric_storage.h",
+    "storage.cc",
+    "storage.h",
+    "types.h",
+  ]
+  deps = [
+    "../../../../gn:default_deps",
+    "../../../../include/perfetto/trace_processor:basic_types",
+    "../../containers",
+  ]
+}
+
+perfetto_unittest_source_set("unittests") {
+  testonly = true
+  sources = [ "storage_unittest.cc" ]
+  deps = [
+    ":storage",
+    "../../../../gn:default_deps",
+    "../../../../gn:gtest_and_gmock",
+  ]
+}
diff --git a/src/trace_processor/db/storage/numeric_storage.cc b/src/trace_processor/db/storage/numeric_storage.cc
new file mode 100644
index 0000000..eb1dd08
--- /dev/null
+++ b/src/trace_processor/db/storage/numeric_storage.cc
@@ -0,0 +1,380 @@
+
+/*
+ * 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/numeric_storage.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+// 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");
+}
+
+// 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");
+}
+
+uint32_t LowerBoundIntrinsic(const void* data,
+                             NumericValue val,
+                             RowMap::Range search_range) {
+  return std::visit(
+      [data, search_range](auto val_data) {
+        using T = decltype(val_data);
+        const T* typed_start = static_cast<const T*>(data);
+        auto lower = std::lower_bound(typed_start + search_range.start,
+                                      typed_start + search_range.end, val_data);
+        return static_cast<uint32_t>(std::distance(typed_start, lower));
+      },
+      val);
+}
+
+uint32_t UpperBoundIntrinsic(const void* data,
+                             NumericValue val,
+                             RowMap::Range search_range) {
+  return std::visit(
+      [data, search_range](auto val_data) {
+        using T = decltype(val_data);
+        const T* typed_start = static_cast<const T*>(data);
+        auto upper = std::upper_bound(typed_start + search_range.start,
+                                      typed_start + search_range.end, val_data);
+        return static_cast<uint32_t>(std::distance(typed_start, upper));
+      },
+      val);
+}
+
+uint32_t LowerBoundExtrinsic(const void* data,
+                             NumericValue val,
+                             uint32_t* indices,
+                             uint32_t indices_count) {
+  return std::visit(
+      [data, indices, indices_count](auto val_data) {
+        using T = decltype(val_data);
+        const T* typed_start = static_cast<const T*>(data);
+        auto lower =
+            std::lower_bound(indices, indices + indices_count, val_data,
+                             [typed_start](uint32_t index, T val) {
+                               return typed_start[index] < val;
+                             });
+        return static_cast<uint32_t>(std::distance(indices, lower));
+      },
+      val);
+}
+
+uint32_t UpperBoundExtrinsic(const void* data,
+                             NumericValue val,
+                             uint32_t* indices,
+                             uint32_t indices_count) {
+  return std::visit(
+      [data, indices, indices_count](auto val_data) {
+        using T = decltype(val_data);
+        const T* typed_start = static_cast<const T*>(data);
+        auto upper =
+            std::upper_bound(indices, indices + indices_count, val_data,
+                             [typed_start](T val, uint32_t index) {
+                               return val < typed_start[index];
+                             });
+        return static_cast<uint32_t>(std::distance(indices, upper));
+      },
+      val);
+}
+
+template <typename T, typename Comparator>
+void TypedLinearSearch(T typed_val,
+                       const T* start,
+                       Comparator comparator,
+                       BitVector::Builder& builder) {
+  // Slow path: we compare <64 elements and append to get us to a word
+  // boundary.
+  const T* ptr = start;
+  uint32_t front_elements = builder.BitsUntilWordBoundaryOrFull();
+  for (uint32_t i = 0; i < front_elements; ++i) {
+    builder.Append(comparator(ptr[i], typed_val));
+  }
+  ptr += 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();
+  for (uint32_t i = 0; i < fast_path_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);
+  }
+  ptr += fast_path_elements;
+
+  // Slow path: we compare <64 elements and append to fill the Builder.
+  uint32_t back_elements = builder.BitsUntilFull();
+  for (uint32_t i = 0; i < back_elements; ++i) {
+    builder.Append(comparator(ptr[i], typed_val));
+  }
+}
+
+template <typename T, typename Comparator>
+void TypedIndexSearch(T typed_val,
+                      const T* start,
+                      uint32_t* indices,
+                      Comparator comparator,
+                      BitVector::Builder& builder) {
+  // Slow path: we compare <64 elements and append to get us to a word
+  // boundary.
+  const T* ptr = start;
+  uint32_t front_elements = builder.BitsUntilWordBoundaryOrFull();
+  for (uint32_t i = 0; i < front_elements; ++i) {
+    builder.Append(comparator(ptr[indices[i]], typed_val));
+  }
+  ptr += 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();
+  for (uint32_t i = 0; i < fast_path_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[indices[i + k]], typed_val);
+      word |= static_cast<uint64_t>(comp_result) << k;
+    }
+    builder.AppendWord(word);
+  }
+  ptr += fast_path_elements;
+
+  // Slow path: we compare <64 elements and append to fill the Builder.
+  uint32_t back_elements = builder.BitsUntilFull();
+  for (uint32_t i = 0; i < back_elements; ++i) {
+    builder.Append(comparator(ptr[indices[i]], typed_val));
+  }
+}
+
+}  // namespace
+
+BitVector NumericStorage::LinearSearch(FilterOp op,
+                                       SqlValue sql_val,
+                                       RowMap::Range range) const {
+  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+  if (op == FilterOp::kIsNotNull)
+    return BitVector(size(), true);
+
+  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
+    return BitVector();
+
+  BitVector::Builder builder(range.end);
+  builder.Skip(range.start);
+  std::visit(
+      [this, range, op, &builder](auto val) {
+        using T = decltype(val);
+        auto* start = static_cast<const T*>(data_) + range.start;
+        std::visit(
+            [start, val, &builder](auto comparator) {
+              TypedLinearSearch(val, start, comparator, builder);
+            },
+            GetFilterOpVariant<T>(op));
+      },
+      *val);
+  return std::move(builder).Build();
+}
+
+BitVector NumericStorage::IndexSearch(FilterOp op,
+                                      SqlValue sql_val,
+                                      uint32_t* indices,
+                                      uint32_t indices_count) const {
+  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+  if (op == FilterOp::kIsNotNull)
+    return BitVector(size(), true);
+
+  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
+    return BitVector();
+
+  BitVector::Builder builder(indices_count);
+  std::visit(
+      [this, indices, op, &builder](auto val) {
+        using T = decltype(val);
+        auto* start = static_cast<const T*>(data_);
+        std::visit(
+            [start, indices, val, &builder](auto comparator) {
+              TypedIndexSearch(val, start, indices, comparator, builder);
+            },
+            GetFilterOpVariant<T>(op));
+      },
+      *val);
+  return std::move(builder).Build();
+}
+
+RowMap::Range NumericStorage::BinarySearchIntrinsic(
+    FilterOp op,
+    SqlValue sql_val,
+    RowMap::Range search_range) const {
+  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+  if (op == FilterOp::kIsNotNull)
+    return RowMap::Range(0, size());
+
+  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
+    return RowMap::Range();
+
+  switch (op) {
+    case FilterOp::kEq:
+      return RowMap::Range(LowerBoundIntrinsic(data_, *val, search_range),
+                           UpperBoundIntrinsic(data_, *val, search_range));
+    case FilterOp::kLe:
+      return RowMap::Range(0, UpperBoundIntrinsic(data_, *val, search_range));
+    case FilterOp::kLt:
+      return RowMap::Range(0, LowerBoundIntrinsic(data_, *val, search_range));
+    case FilterOp::kGe:
+      return RowMap::Range(LowerBoundIntrinsic(data_, *val, search_range),
+                           size_);
+    case FilterOp::kGt:
+      return RowMap::Range(UpperBoundIntrinsic(data_, *val, search_range),
+                           size_);
+    case FilterOp::kNe:
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+    case FilterOp::kGlob:
+      return RowMap::Range();
+  }
+  return RowMap::Range();
+}
+
+RowMap::Range NumericStorage::BinarySearchExtrinsic(
+    FilterOp op,
+    SqlValue sql_val,
+    uint32_t* indices,
+    uint32_t indices_count) const {
+  std::optional<NumericValue> val = GetNumericTypeVariant(type_, sql_val);
+
+  if (op == FilterOp::kIsNotNull)
+    return RowMap::Range(0, size());
+
+  if (!val.has_value() || op == FilterOp::kIsNull || op == FilterOp::kGlob)
+    return RowMap::Range();
+
+  switch (op) {
+    case FilterOp::kEq:
+      return RowMap::Range(
+          LowerBoundExtrinsic(data_, *val, indices, indices_count),
+          UpperBoundExtrinsic(data_, *val, indices, indices_count));
+    case FilterOp::kLe:
+      return RowMap::Range(
+          0, UpperBoundExtrinsic(data_, *val, indices, indices_count));
+    case FilterOp::kLt:
+      return RowMap::Range(
+          0, LowerBoundExtrinsic(data_, *val, indices, indices_count));
+    case FilterOp::kGe:
+      return RowMap::Range(
+          LowerBoundExtrinsic(data_, *val, indices, indices_count), size_);
+    case FilterOp::kGt:
+      return RowMap::Range(
+          UpperBoundExtrinsic(data_, *val, indices, indices_count), size_);
+    case FilterOp::kNe:
+    case FilterOp::kIsNull:
+    case FilterOp::kIsNotNull:
+    case FilterOp::kGlob:
+      return RowMap::Range();
+  }
+  return RowMap::Range();
+}
+
+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);
+}
+
+void NumericStorage::Sort(uint32_t*, uint32_t) const {}
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/numeric_storage.h b/src/trace_processor/db/storage/numeric_storage.h
new file mode 100644
index 0000000..ab802de
--- /dev/null
+++ b/src/trace_processor/db/storage/numeric_storage.h
@@ -0,0 +1,68 @@
+/*
+ * 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_NUMERIC_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_NUMERIC_STORAGE_H_
+
+#include <variant>
+
+#include "src/trace_processor/db/storage/storage.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Storage for all numeric type data (i.e. doubles, int32, int64, uint32).
+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 Sort(uint32_t* rows, uint32_t rows_size) const override;
+
+  BitVector LinearSearch(FilterOp op,
+                         SqlValue val,
+                         RowMap::Range) const override;
+
+  BitVector IndexSearch(FilterOp op,
+                        SqlValue value,
+                        uint32_t* indices,
+                        uint32_t indices_count) const override;
+
+  RowMap::Range BinarySearchIntrinsic(
+      FilterOp op,
+      SqlValue val,
+      RowMap::Range search_range) const override;
+
+  RowMap::Range BinarySearchExtrinsic(FilterOp op,
+                                      SqlValue val,
+                                      uint32_t* indices,
+                                      uint32_t indices_count) const override;
+
+  uint32_t size() const override { return size_; }
+
+ private:
+  const ColumnType type_ = ColumnType::kDummy;
+  const void* data_ = nullptr;
+  const uint32_t size_ = 0;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_NUMERIC_STORAGE_H_
diff --git a/src/trace_processor/db/storage.cc b/src/trace_processor/db/storage/storage.cc
similarity index 88%
rename from src/trace_processor/db/storage.cc
rename to src/trace_processor/db/storage/storage.cc
index 4799d04..78ed335 100644
--- a/src/trace_processor/db/storage.cc
+++ b/src/trace_processor/db/storage/storage.cc
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/db/storage.h"
+#include "src/trace_processor/db/storage/storage.h"
 
 namespace perfetto {
 namespace trace_processor {
-namespace column {
+namespace storage {
 
 Storage::~Storage() = default;
 
-}  // namespace column
+}  // namespace storage
 }  // namespace trace_processor
 }  // namespace perfetto
diff --git a/src/trace_processor/db/storage/storage.h b/src/trace_processor/db/storage/storage.h
new file mode 100644
index 0000000..1021723
--- /dev/null
+++ b/src/trace_processor/db/storage/storage.h
@@ -0,0 +1,94 @@
+/*
+ * 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_STORAGE_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_STORAGE_H_
+
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/db/storage/types.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+
+// Backing storage for columnar tables.
+class Storage {
+ public:
+  virtual ~Storage();
+
+  // Searches for elements which match |op| and |value| between |range.start|
+  // and |range.end|.
+  //
+  // Returns a BitVector of size |range.end| with the position of the 1s
+  // representing the positions which matched and 0s otherwise. The first
+  // |range.start| number of elements will be zero.
+  virtual BitVector LinearSearch(FilterOp op,
+                                 SqlValue value,
+                                 RowMap::Range range) const = 0;
+
+  // Searches for elements which match |op| and |value| at the positions given
+  // by |indices| array.
+  //
+  // Returns a BitVector of size |indices_count| with the position of the 1s
+  // representing the positions which matched and 0s otherwise.
+  virtual BitVector IndexSearch(FilterOp op,
+                                SqlValue value,
+                                uint32_t* indices,
+                                uint32_t indices_count) const = 0;
+
+  // Binary searches for elements which match |op| and |value| between
+  // |range.start_index| and |range.end_index|.
+  //
+  // Returns a range, indexing the storage, where all elements in that range
+  // match the constraint.
+  //
+  // Note: the caller *must* know that the elements in this storage are sorted;
+  // it is an error to call this method otherwise.
+  virtual RowMap::Range BinarySearchIntrinsic(FilterOp op,
+                                              SqlValue value,
+                                              RowMap::Range range) const = 0;
+
+  // Binary searches for elements which match |op| and |value| only considering
+  // the elements in |indices|.
+  //
+  // Returns a sub-Range of Range[0, indices_count) which indicates the
+  // positions of elements in |indices| which match.
+  //
+  // Note: the caller *must* known that the elements in storage will be sorted
+  // by the elements in |indices|; it is undefined behaviour to call this method
+  // otherwise.
+  virtual RowMap::Range BinarySearchExtrinsic(FilterOp op,
+                                              SqlValue value,
+                                              uint32_t* indices,
+                                              uint32_t indices_count) const = 0;
+
+  // Sorts |rows| in ascending order with the comparator:
+  // data[rows[a]] < data[rows[b]].
+  virtual void Sort(uint32_t* rows, uint32_t rows_size) const = 0;
+
+  // Stable sorts |rows| in ascending order with the comparator:
+  // data[rows[a]] < data[rows[b]].
+  virtual void StableSort(uint32_t* rows, uint32_t rows_size) const = 0;
+
+  // Number of elements in stored data.
+  virtual uint32_t size() const = 0;
+};
+
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_STORAGE_H_
diff --git a/src/trace_processor/db/storage/storage_unittest.cc b/src/trace_processor/db/storage/storage_unittest.cc
new file mode 100644
index 0000000..6d737b9
--- /dev/null
+++ b/src/trace_processor/db/storage/storage_unittest.cc
@@ -0,0 +1,122 @@
+/*
+ * 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/storage/numeric_storage.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace storage {
+namespace {
+
+using Range = RowMap::Range;
+
+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, 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 bv =
+      storage.LinearSearch(FilterOp::kGe, SqlValue::Long(100), Range(0, 128));
+
+  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);
+  Range range = storage.BinarySearchIntrinsic(
+      FilterOp::kGe, SqlValue::Long(100), Range(0, 128));
+
+  ASSERT_EQ(range.size(), 28u);
+  ASSERT_EQ(range.start, 100u);
+  ASSERT_EQ(range.end, 128u);
+}
+
+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);
+
+  std::optional<Range> range = storage.BinarySearchExtrinsic(
+      FilterOp::kGe, SqlValue::Long(60), sorted_order.data(), 10);
+
+  ASSERT_EQ(range->size(), 4u);
+  ASSERT_EQ(range->start, 6u);
+  ASSERT_EQ(range->end, 10u);
+}
+
+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);
+
+  std::optional<Range> range = storage.BinarySearchExtrinsic(
+      FilterOp::kLt, SqlValue::Long(60), sorted_order.data(), 10);
+
+  ASSERT_EQ(range->size(), 6u);
+  ASSERT_EQ(range->start, 0u);
+  ASSERT_EQ(range->end, 6u);
+}
+
+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);
+
+  std::optional<Range> range = storage.BinarySearchExtrinsic(
+      FilterOp::kEq, SqlValue::Long(60), sorted_order.data(), 10);
+
+  ASSERT_EQ(range->size(), 1u);
+  ASSERT_EQ(range->start, 6u);
+  ASSERT_EQ(range->end, 7u);
+}
+
+}  // namespace
+}  // namespace storage
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/db/storage/types.h b/src/trace_processor/db/storage/types.h
new file mode 100644
index 0000000..12ee297
--- /dev/null
+++ b/src/trace_processor/db/storage/types.h
@@ -0,0 +1,71 @@
+/*
+ * 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_TYPES_H_
+#define SRC_TRACE_PROCESSOR_DB_STORAGE_TYPES_H_
+
+#include "perfetto/trace_processor/basic_types.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Represents the possible filter operations on a column.
+enum class FilterOp {
+  kEq,
+  kNe,
+  kGt,
+  kLt,
+  kGe,
+  kLe,
+  kIsNull,
+  kIsNotNull,
+  kGlob,
+};
+
+// Represents a constraint on a column.
+struct Constraint {
+  uint32_t col_idx;
+  FilterOp op;
+  SqlValue value;
+};
+
+// Represents an order by operation on a column.
+struct Order {
+  uint32_t col_idx;
+  bool desc;
+};
+
+// The enum type of the column.
+// Public only to stop GCC complaining about templates being defined in a
+// non-namespace scope (see ColumnTypeHelper below).
+enum class ColumnType {
+  // Standard primitive types.
+  kInt32,
+  kUint32,
+  kInt64,
+  kDouble,
+  kString,
+
+  // Types generated on the fly.
+  kId,
+
+  // Types which don't have any data backing them.
+  kDummy,
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_DB_STORAGE_TYPES_H_
diff --git a/src/trace_processor/db/storage_overlay.h b/src/trace_processor/db/storage_overlay.h
deleted file mode 100644
index 2690919..0000000
--- a/src/trace_processor/db/storage_overlay.h
+++ /dev/null
@@ -1,44 +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.
- */
-
-#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*);
-  void Filter(FilterOp, SqlValue, RowMap&) override;
-  void Sort(std::vector<uint32_t>&) 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
deleted file mode 100644
index 3b8f7c4..0000000
--- a/src/trace_processor/db/storage_unittest.cc
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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/trace_processor/db/numeric_storage.h"
-
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace column {
-
-namespace {
-
-TEST(StorageUnittest, 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(), ColumnType::kUint32);
-  RowMap rm(0, 9);
-  storage.StableSort(out);
-
-  std::vector<uint32_t> stable_out{0, 3, 6, 1, 4, 7, 2, 5, 8};
-  ASSERT_EQ(out, stable_out);
-}
-
-TEST(StorageUnittest, 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(), ColumnType::kUint32);
-  RowMap rm(0, 9);
-  storage.StableSort(out);
-
-  std::vector<uint32_t> stable_out{0, 6, 3, 1, 7, 4, 2, 5, 8};
-  ASSERT_EQ(out, stable_out);
-}
-
-TEST(StorageUnittest, CompareSlow) {
-  std::vector<uint32_t> data_vec{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
-  NumericStorage storage(data_vec.data(), ColumnType::kUint32);
-  BitVector::Builder builder(10);
-  storage.CompareSlow(FilterOp::kGe, SqlValue::Long(5), data_vec.data(), 10,
-                      builder);
-  BitVector bv = std::move(builder).Build();
-
-  ASSERT_EQ(bv.CountSetBits(), 5u);
-  ASSERT_EQ(bv.IndexOfNthSet(0), 5u);
-}
-
-TEST(StorageUnittest, CompareFast) {
-  std::vector<uint32_t> data_vec;
-  for (uint32_t i = 0; i < 128; ++i) {
-    data_vec.push_back(i);
-  }
-  NumericStorage storage(data_vec.data(), ColumnType::kUint32);
-  BitVector::Builder builder(128);
-  storage.CompareFast(FilterOp::kGe, SqlValue::Long(100), data_vec.data(), 128,
-                      builder);
-  BitVector bv = std::move(builder).Build();
-
-  ASSERT_EQ(bv.CountSetBits(), 28u);
-  ASSERT_EQ(bv.IndexOfNthSet(0), 100u);
-}
-
-TEST(StorageUnittest, CompareSorted) {
-  std::vector<uint32_t> data_vec;
-  for (uint32_t i = 0; i < 128; ++i) {
-    data_vec.push_back(i);
-  }
-  NumericStorage storage(data_vec.data(), ColumnType::kUint32);
-  RowMap rm(0, 128);
-  storage.CompareSorted(FilterOp::kGe, SqlValue::Long(100), data_vec.data(),
-                        128, rm);
-
-  ASSERT_EQ(rm.size(), 28u);
-  ASSERT_EQ(rm.Get(0), 100u);
-}
-
-}  // 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
deleted file mode 100644
index bc169b9..0000000
--- a/src/trace_processor/db/storage_variants.h
+++ /dev/null
@@ -1,108 +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.
- */
-#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/importers/common/parser_types.h b/src/trace_processor/importers/common/parser_types.h
index 2b5648a..7290626 100644
--- a/src/trace_processor/importers/common/parser_types.h
+++ b/src/trace_processor/importers/common/parser_types.h
@@ -35,10 +35,12 @@
 
 struct alignas(8) InlineSchedWaking {
   int32_t pid;
-  int32_t target_cpu;
-  int32_t prio;
+  uint16_t target_cpu;
+  uint16_t prio;
   StringPool::Id comm;
+  uint16_t common_flags;
 };
+static_assert(sizeof(InlineSchedWaking) == 16);
 
 struct alignas(8) JsonEvent {
   std::string value;
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index d0d58ba..6ccf70d 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -71,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"
@@ -232,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")),
@@ -870,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:
@@ -1060,9 +1069,9 @@
   }
   using protos::pbzero::FtraceEvent;
   SchedEventTracker* sched_tracker = SchedEventTracker::GetOrCreate(context_);
-  sched_tracker->PushSchedWakingCompact(cpu, ts,
-                                        static_cast<uint32_t>(data.pid),
-                                        data.target_cpu, data.prio, data.comm);
+  sched_tracker->PushSchedWakingCompact(
+      cpu, ts, static_cast<uint32_t>(data.pid), data.target_cpu, data.prio,
+      data.comm, data.common_flags);
   return util::OkStatus();
 }
 
@@ -2924,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);
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/ftrace_tokenizer.cc b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
index 67fccfe..c699ab9 100644
--- a/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_tokenizer.cc
@@ -247,6 +247,7 @@
   auto tcpu_it = compact.waking_target_cpu(&parse_error);
   auto prio_it = compact.waking_prio(&parse_error);
   auto comm_it = compact.waking_comm_index(&parse_error);
+  auto common_flags_it = compact.waking_common_flags(&parse_error);
 
   for (; timestamp_it && pid_it && tcpu_it && prio_it && comm_it;
        ++timestamp_it, ++pid_it, ++tcpu_it, ++prio_it, ++comm_it) {
@@ -261,8 +262,13 @@
     event.comm = string_table[*comm_it];
 
     event.pid = *pid_it;
-    event.target_cpu = *tcpu_it;
-    event.prio = *prio_it;
+    event.target_cpu = static_cast<uint16_t>(*tcpu_it);
+    event.prio = static_cast<uint16_t>(*prio_it);
+
+    if (common_flags_it) {
+      event.common_flags = static_cast<uint16_t>(*common_flags_it);
+      common_flags_it++;
+    }
 
     base::StatusOr<int64_t> timestamp =
         ResolveTraceTime(context_, clock_id, event_timestamp);
diff --git a/src/trace_processor/importers/ftrace/sched_event_tracker.cc b/src/trace_processor/importers/ftrace/sched_event_tracker.cc
index 68fe3b7..09459c4 100644
--- a/src/trace_processor/importers/ftrace/sched_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker.cc
@@ -212,9 +212,10 @@
 void SchedEventTracker::PushSchedWakingCompact(uint32_t cpu,
                                                int64_t ts,
                                                uint32_t wakee_pid,
-                                               int32_t target_cpu,
-                                               int32_t prio,
-                                               StringId comm_id) {
+                                               uint16_t target_cpu,
+                                               uint16_t prio,
+                                               StringId comm_id,
+                                               uint16_t common_flags) {
   // At this stage all events should be globally timestamp ordered.
   if (ts < context_->event_tracker->max_timestamp()) {
     PERFETTO_ELOG(
@@ -239,10 +240,15 @@
   auto curr_utid = pending_sched->last_utid;
 
   if (PERFETTO_LIKELY(context_->config.ingest_ftrace_in_raw_table)) {
+    tables::FtraceEventTable::Row row;
+    row.ts = ts;
+    row.name = sched_waking_id_;
+    row.cpu = cpu;
+    row.utid = curr_utid;
+    row.common_flags = common_flags;
+
     // Add an entry to the raw table.
-    RawId id = context_->storage->mutable_ftrace_event_table()
-                   ->Insert({ts, sched_waking_id_, cpu, curr_utid})
-                   .id;
+    RawId id = context_->storage->mutable_ftrace_event_table()->Insert(row).id;
 
     using SW = protos::pbzero::SchedWakingFtraceEvent;
     auto inserter = context_->args_tracker->AddArgsTo(id);
@@ -258,8 +264,8 @@
 
   // Add a waking entry to the ThreadState table.
   auto wakee_utid = context_->process_tracker->GetOrCreateThread(wakee_pid);
-  ThreadStateTracker::GetOrCreate(context_)->PushWakingEvent(ts, wakee_utid,
-                                                             curr_utid);
+  ThreadStateTracker::GetOrCreate(context_)->PushWakingEvent(
+      ts, wakee_utid, curr_utid, common_flags);
 }
 
 PERFETTO_ALWAYS_INLINE
diff --git a/src/trace_processor/importers/ftrace/sched_event_tracker.h b/src/trace_processor/importers/ftrace/sched_event_tracker.h
index beebcfe..745b7c3 100644
--- a/src/trace_processor/importers/ftrace/sched_event_tracker.h
+++ b/src/trace_processor/importers/ftrace/sched_event_tracker.h
@@ -73,9 +73,10 @@
   void PushSchedWakingCompact(uint32_t cpu,
                               int64_t ts,
                               uint32_t wakee_pid,
-                              int32_t target_cpu,
-                              int32_t prio,
-                              StringId comm_id);
+                              uint16_t target_cpu,
+                              uint16_t prio,
+                              StringId comm_id,
+                              uint16_t common_flags);
 
  private:
   // Information retained from the preceding sched_switch seen on a given cpu.
diff --git a/src/trace_processor/importers/ftrace/thread_state_tracker.cc b/src/trace_processor/importers/ftrace/thread_state_tracker.cc
index 3c6b123..a80d6a4 100644
--- a/src/trace_processor/importers/ftrace/thread_state_tracker.cc
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker.cc
@@ -50,7 +50,8 @@
 
 void ThreadStateTracker::PushWakingEvent(int64_t event_ts,
                                          UniqueTid utid,
-                                         UniqueTid waker_utid) {
+                                         UniqueTid waker_utid,
+                                         std::optional<uint16_t> common_flags) {
   // Only open new runnable state if thread already had a sched switch event.
   if (!HasPreviousRowNumbersForUtid(utid)) {
     return;
@@ -62,12 +63,20 @@
   // is running), we just ignore the waking event. See b/186509316 for details
   // and an example on when this happens. Only blocked events can be waken up.
   if (!IsBlocked(last_row_ref.state())) {
+    // If we receive a waking event while we are not blocked, we ignore this
+    // in the |thread_state| table but we track in the |sched_wakeup| table.
+    // The |thread_state_id| in |sched_wakeup| is the current running/runnable
+    // event.
+    storage_->mutable_spurious_sched_wakeup_table()->Insert(
+        {event_ts, prev_row_numbers_for_thread_[utid]->last_row.row_number(),
+         CommonFlagsToIrqContext(*common_flags), utid, waker_utid});
     return;
   }
 
   // Close the sleeping state and open runnable state.
   ClosePendingState(event_ts, utid, false);
-  AddOpenState(event_ts, utid, runnable_string_id_, std::nullopt, waker_utid);
+  AddOpenState(event_ts, utid, runnable_string_id_, std::nullopt, waker_utid,
+               common_flags);
 }
 
 void ThreadStateTracker::PushNewTaskEvent(int64_t event_ts,
@@ -102,8 +111,9 @@
 void ThreadStateTracker::AddOpenState(int64_t ts,
                                       UniqueTid utid,
                                       StringId state,
-                                      std::optional<uint32_t> cpu,
-                                      std::optional<UniqueTid> waker_utid) {
+                                      std::optional<uint16_t> cpu,
+                                      std::optional<UniqueTid> waker_utid,
+                                      std::optional<uint16_t> common_flags) {
   // Ignore utid 0 because it corresponds to the swapper thread which doesn't
   // make sense to insert.
   if (utid == 0)
@@ -117,6 +127,10 @@
   row.dur = -1;
   row.utid = utid;
   row.state = state;
+  if (common_flags.has_value()) {
+    row.irq_context = CommonFlagsToIrqContext(*common_flags);
+  }
+
   auto row_num = storage_->mutable_thread_state_table()->Insert(row).row_number;
 
   if (utid >= prev_row_numbers_for_thread_.size()) {
@@ -136,6 +150,14 @@
   }
 }
 
+uint32_t ThreadStateTracker::CommonFlagsToIrqContext(uint32_t common_flags) {
+  // If common_flags contains TRACE_FLAG_HARDIRQ | TRACE_FLAG_SOFTIRQ, wakeup
+  // was emitted in interrupt context.
+  // See:
+  // https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/include/trace/trace_events.h
+  return common_flags & (0x08 | 0x10) ? 1 : 0;
+}
+
 void ThreadStateTracker::ClosePendingState(int64_t end_ts,
                                            UniqueTid utid,
                                            bool data_loss) {
diff --git a/src/trace_processor/importers/ftrace/thread_state_tracker.h b/src/trace_processor/importers/ftrace/thread_state_tracker.h
index 7faae9c..839ad31 100644
--- a/src/trace_processor/importers/ftrace/thread_state_tracker.h
+++ b/src/trace_processor/importers/ftrace/thread_state_tracker.h
@@ -49,7 +49,10 @@
                             UniqueTid next_utid);
 
   // Will add a runnable state for utid and close the previously blocked one.
-  void PushWakingEvent(int64_t event_ts, UniqueTid utid, UniqueTid waker_utid);
+  void PushWakingEvent(int64_t event_ts,
+                       UniqueTid utid,
+                       UniqueTid waker_utid,
+                       std::optional<uint16_t> common_flags = std::nullopt);
 
   // Will add a runnable state for utid. For a new task there are no previous
   // states to close.
@@ -64,10 +67,13 @@
   void AddOpenState(int64_t ts,
                     UniqueTid utid,
                     StringId state,
-                    std::optional<uint32_t> cpu = std::nullopt,
-                    std::optional<UniqueTid> waker_utid = std::nullopt);
+                    std::optional<uint16_t> cpu = std::nullopt,
+                    std::optional<UniqueTid> waker_utid = std::nullopt,
+                    std::optional<uint16_t> common_flags = std::nullopt);
   void ClosePendingState(int64_t end_ts, UniqueTid utid, bool data_loss);
 
+  uint32_t CommonFlagsToIrqContext(uint32_t common_flags);
+
   bool IsRunning(StringId state);
   bool IsBlocked(StringId state);
   bool IsRunnable(StringId state);
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 1d55081..e9df6c46 100644
--- a/src/trace_processor/importers/proto/android_camera_event_module.h
+++ b/src/trace_processor/importers/proto/android_camera_event_module.h
@@ -24,6 +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/sched_tables_py.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 94a9cb5..fece007 100644
--- a/src/trace_processor/importers/proto/android_probes_module.cc
+++ b/src/trace_processor/importers/proto/android_probes_module.cc
@@ -181,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());
@@ -207,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 7521061..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);
@@ -118,7 +119,9 @@
   }
 }
 
-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);
   }
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/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/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/statsd_module.h b/src/trace_processor/importers/proto/statsd_module.h
index 430b7a2..653c1eb 100644
--- a/src/trace_processor/importers/proto/statsd_module.h
+++ b/src/trace_processor/importers/proto/statsd_module.h
@@ -26,6 +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/sched_tables_py.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/system_probes_parser.cc b/src/trace_processor/importers/proto/system_probes_parser.cc
index 999d365..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) {
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/iterator_impl.cc b/src/trace_processor/iterator_impl.cc
index 5ee1e1d..c21c27e 100644
--- a/src/trace_processor/iterator_impl.cc
+++ b/src/trace_processor/iterator_impl.cc
@@ -18,6 +18,7 @@
 
 #include "perfetto/base/time.h"
 #include "perfetto/trace_processor/trace_processor_storage.h"
+#include "src/trace_processor/sqlite/perfetto_sql_engine.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/trace_processor_impl.h"
@@ -25,17 +26,12 @@
 namespace perfetto {
 namespace trace_processor {
 
-IteratorImpl::IteratorImpl(TraceProcessorImpl* trace_processor,
-                           sqlite3* db,
-                           base::Status status,
-                           ScopedStmt stmt,
-                           StmtMetadata metadata,
-                           uint32_t sql_stats_row)
+IteratorImpl::IteratorImpl(
+    TraceProcessorImpl* trace_processor,
+    base::StatusOr<PerfettoSqlEngine::ExecutionResult> result,
+    uint32_t sql_stats_row)
     : trace_processor_(trace_processor),
-      db_(db),
-      status_(std::move(status)),
-      stmt_(std::move(stmt)),
-      stmt_metadata_(std::move(metadata)),
+      result_(std::move(result)),
       sql_stats_row_(sql_stats_row) {}
 
 IteratorImpl::~IteratorImpl() {
diff --git a/src/trace_processor/iterator_impl.h b/src/trace_processor/iterator_impl.h
index 0c8b372..ec587f2 100644
--- a/src/trace_processor/iterator_impl.h
+++ b/src/trace_processor/iterator_impl.h
@@ -28,6 +28,7 @@
 #include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/iterator.h"
 #include "perfetto/trace_processor/status.h"
+#include "src/trace_processor/sqlite/perfetto_sql_engine.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sqlite_utils.h"
 
@@ -38,17 +39,8 @@
 
 class IteratorImpl {
  public:
-  struct StmtMetadata {
-    uint32_t column_count = 0;
-    uint32_t statement_count = 0;
-    uint32_t statement_count_with_output = 0;
-  };
-
   IteratorImpl(TraceProcessorImpl* impl,
-               sqlite3* db,
-               base::Status,
-               ScopedStmt,
-               StmtMetadata,
+               base::StatusOr<PerfettoSqlEngine::ExecutionResult>,
                uint32_t sql_stats_row);
   ~IteratorImpl();
 
@@ -60,8 +52,6 @@
 
   // Methods called by the base Iterator class.
   bool Next() {
-    PERFETTO_DCHECK(stmt_ || !status_.ok());
-
     if (!called_next_) {
       // Delegate to the cc file to prevent trace_storage.h include in this
       // file.
@@ -78,46 +68,49 @@
       // (i.e. implement operator bool, make Next return nothing similar to C++
       // iterators); however, too many clients depend on the current behavior so
       // we have to keep the API as is.
-      return status_.ok() && !sqlite_utils::IsStmtDone(*stmt_);
+      return result_.ok() && !sqlite_utils::IsStmtDone(*result_->stmt);
     }
 
-    if (!status_.ok())
+    if (!result_.ok())
       return false;
 
-    int ret = sqlite3_step(*stmt_);
+    int ret = sqlite3_step(*result_->stmt);
     if (PERFETTO_UNLIKELY(ret != SQLITE_ROW && ret != SQLITE_DONE)) {
-      status_ = base::ErrStatus("%s", sqlite_utils::FormatErrorMessage(
-                                          stmt_.get(), std::nullopt, db_, ret)
-                                          .c_message());
-      stmt_.reset();
+      result_ =
+          base::ErrStatus("%s", sqlite_utils::FormatErrorMessage(
+                                    result_->stmt.get(), std::nullopt,
+                                    sqlite3_db_handle(result_->stmt.get()), ret)
+                                    .c_message());
       return false;
     }
     return ret == SQLITE_ROW;
   }
 
-  SqlValue Get(uint32_t col) {
+  SqlValue Get(uint32_t col) const {
+    PERFETTO_DCHECK(result_.ok());
+
     auto column = static_cast<int>(col);
-    auto col_type = sqlite3_column_type(*stmt_, column);
+    auto col_type = sqlite3_column_type(*result_->stmt, column);
     SqlValue value;
     switch (col_type) {
       case SQLITE_INTEGER:
         value.type = SqlValue::kLong;
-        value.long_value = sqlite3_column_int64(*stmt_, column);
+        value.long_value = sqlite3_column_int64(*result_->stmt, column);
         break;
       case SQLITE_TEXT:
         value.type = SqlValue::kString;
-        value.string_value =
-            reinterpret_cast<const char*>(sqlite3_column_text(*stmt_, column));
+        value.string_value = reinterpret_cast<const char*>(
+            sqlite3_column_text(*result_->stmt, column));
         break;
       case SQLITE_FLOAT:
         value.type = SqlValue::kDouble;
-        value.double_value = sqlite3_column_double(*stmt_, column);
+        value.double_value = sqlite3_column_double(*result_->stmt, column);
         break;
       case SQLITE_BLOB:
         value.type = SqlValue::kBytes;
-        value.bytes_value = sqlite3_column_blob(*stmt_, column);
+        value.bytes_value = sqlite3_column_blob(*result_->stmt, column);
         value.bytes_count =
-            static_cast<size_t>(sqlite3_column_bytes(*stmt_, column));
+            static_cast<size_t>(sqlite3_column_bytes(*result_->stmt, column));
         break;
       case SQLITE_NULL:
         value.type = SqlValue::kNull;
@@ -126,18 +119,24 @@
     return value;
   }
 
-  std::string GetColumnName(uint32_t col) {
-    return stmt_ ? sqlite3_column_name(*stmt_, static_cast<int>(col)) : "";
+  std::string GetColumnName(uint32_t col) const {
+    return result_.ok() && result_->stmt
+               ? sqlite3_column_name(*result_->stmt, static_cast<int>(col))
+               : "";
   }
 
-  base::Status Status() { return status_; }
+  base::Status Status() const { return result_.status(); }
 
-  uint32_t ColumnCount() { return stmt_metadata_.column_count; }
+  uint32_t ColumnCount() const {
+    return result_.ok() ? result_->column_count : 0;
+  }
 
-  uint32_t StatementCount() { return stmt_metadata_.statement_count; }
+  uint32_t StatementCount() const {
+    return result_.ok() ? result_->statement_count : 0;
+  }
 
-  uint32_t StatementCountWithOutput() {
-    return stmt_metadata_.statement_count_with_output;
+  uint32_t StatementCountWithOutput() const {
+    return result_.ok() ? result_->statement_count_with_output : 0;
   }
 
  private:
@@ -156,11 +155,7 @@
   void RecordFirstNextInSqlStats();
 
   ScopedTraceProcessor trace_processor_;
-  sqlite3* db_ = nullptr;
-  base::Status status_;
-
-  ScopedStmt stmt_;
-  StmtMetadata stmt_metadata_;
+  base::StatusOr<PerfettoSqlEngine::ExecutionResult> result_;
 
   uint32_t sql_stats_row_ = 0;
   bool called_next_ = false;
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..a5cfd5a 100644
--- a/src/trace_processor/metrics/sql/android/android_batt.sql
+++ b/src/trace_processor/metrics/sql/android/android_batt.sql
@@ -14,6 +14,7 @@
 -- limitations under the License.
 --
 SELECT IMPORT('android.battery');
+SELECT IMPORT('android.battery_stats');
 
 DROP VIEW IF EXISTS battery_view;
 CREATE VIEW battery_view AS
@@ -49,8 +50,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 +71,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',
@@ -171,7 +183,26 @@
        END AS slice_name,
        'Plug type' AS track_name,
        'slice' AS track_type
-FROM plug_type_span;
+FROM plug_type_span
+UNION ALL
+SELECT *
+FROM (
+  SELECT ts,
+         dur,
+         value_name AS slice_name,
+         CASE track_name
+         WHEN 'battery_stats.mobile_radio' THEN 'Cellular radio'
+         WHEN 'battery_stats.data_conn' THEN 'Cellular connection'
+         WHEN 'battery_stats.phone_signal_strength' THEN 'Cellular strength'
+         WHEN 'battery_stats.wifi_radio' THEN 'WiFi radio'
+         WHEN 'battery_stats.wifi_suppl' THEN 'Wifi supplicant state'
+         WHEN 'battery_stats.wifi_signal_strength' THEN 'WiFi strength'
+         ELSE NULL
+         END AS track_name,
+         'slice' AS track_type
+  FROM android_battery_stats_state
+)
+WHERE track_name IS NOT NULL;
 
 DROP VIEW IF EXISTS android_batt_output;
 CREATE VIEW android_batt_output AS
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..0724ba7 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 (
@@ -107,7 +126,20 @@
             OR s.name GLOB '*CancellableContinuationImpl*'
             OR s.name GLOB 'relayoutWindow*'
             OR s.name GLOB 'ImageDecoder#decode*'
+            OR s.name GLOB 'NotificationStackScrollLayout#onMeasure'
+            OR s.name GLOB 'ExpNotRow#*'
         )
+    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_monitor_contention.sql b/src/trace_processor/metrics/sql/android/android_monitor_contention.sql
index 7cfaeb8..52e1693 100644
--- a/src/trace_processor/metrics/sql/android/android_monitor_contention.sql
+++ b/src/trace_processor/metrics/sql/android/android_monitor_contention.sql
@@ -35,7 +35,10 @@
         'waiter_count', waiter_count,
         'blocking_thread_name', blocking_thread_name,
         'blocked_thread_name', blocked_thread_name,
+        'blocked_thread_tid', blocked_thread_tid,
+        'blocking_thread_tid', blocking_thread_tid,
         'process_name', process_name,
+        'pid', pid,
         'is_blocked_thread_main', is_blocked_thread_main,
         'is_blocking_thread_main', is_blocking_thread_main,
         'binder_reply_ts', binder_reply_ts,
diff --git a/src/trace_processor/metrics/sql/android/android_startup.sql b/src/trace_processor/metrics/sql/android/android_startup.sql
index ab76b6c..a9823af 100644
--- a/src/trace_processor/metrics/sql/android/android_startup.sql
+++ b/src/trace_processor/metrics/sql/android/android_startup.sql
@@ -82,6 +82,19 @@
   '
 );
 
+-- Given a launch id and GLOB for a slice name, returns the N longest slice name and duration.
+SELECT CREATE_VIEW_FUNCTION(
+  'GET_LONG_SLICES_FOR_LAUNCH(startup_id INT, slice_name STRING, top_n INT)',
+  'slice_name STRING, slice_dur INT',
+  '
+    SELECT slice_name, slice_dur
+    FROM android_thread_slices_for_all_startups s
+    WHERE s.startup_id = $startup_id AND s.slice_name GLOB $slice_name
+    ORDER BY slice_dur DESC
+    LIMIT $top_n
+  '
+);
+
 -- Define the view
 DROP VIEW IF EXISTS startup_view;
 CREATE VIEW startup_view AS
@@ -213,6 +226,10 @@
       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,
@@ -287,10 +304,7 @@
       SELECT RepeatedField(AndroidStartupMetric_VerifyClass(
         'name', STR_SPLIT(slice_name, "VerifyClass ", 1),
         'dur_ns', slice_dur))
-      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
+      FROM GET_LONG_SLICES_FOR_LAUNCH(launches.startup_id, "VerifyClass *", 5)
     ),
     'startup_concurrent_to_launch', (
       SELECT RepeatedField(package)
@@ -298,6 +312,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,
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..3e83f87
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/network_activity_template.sql
@@ -0,0 +1,75 @@
+--
+-- 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.
+DROP VIEW IF EXISTS {{view_name}};
+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+dur) 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/android/startup/thread_state_breakdown.sql b/src/trace_processor/metrics/sql/android/startup/thread_state_breakdown.sql
index b3939bb..4ef1fc4 100644
--- a/src/trace_processor/metrics/sql/android/startup/thread_state_breakdown.sql
+++ b/src/trace_processor/metrics/sql/android/startup/thread_state_breakdown.sql
@@ -38,8 +38,10 @@
 CREATE TABLE launch_thread_state_io_wait_dur_sum AS
 SELECT startup_id, state, is_main_thread, thread_name, io_wait, SUM(dur) AS dur
 FROM launch_threads_by_thread_state l
+JOIN android_startup_processes p USING (startup_id)
 WHERE
-  is_main_thread
+  -- If it is a main thread, only add it if it is the lauching thread.
+  (is_main_thread AND p.startup_type NOT NULL)
   -- Allowlist specific threads which need this. Do not add to this list
   -- without careful consideration as every thread added here can cause
   -- memory usage to balloon.
diff --git a/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_v2.sql b/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_v2.sql
index 9461d2c..30f6e09 100644
--- a/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_v2.sql
+++ b/src/trace_processor/metrics/sql/chrome/chrome_scroll_jank_v2.sql
@@ -18,6 +18,59 @@
 
 SELECT RUN_METRIC('chrome/event_latency_scroll_jank_cause.sql');
 
+DROP VIEW IF EXISTS __chrome_scroll_jank_v2_scroll_processing;
+
+CREATE VIEW __chrome_scroll_jank_v2_scroll_processing
+AS
+SELECT COALESCE(SUM(jank.dur), 0) / 1.0e6 AS scroll_processing_ms
+FROM
+  scroll_event_latency_jank AS jank
+LEFT JOIN
+  event_latency_scroll_jank_cause AS cause
+  ON
+    jank.id = cause.slice_id;
+
+DROP VIEW IF EXISTS __chrome_scroll_jank_v2_causes_and_durations;
+
+CREATE VIEW __chrome_scroll_jank_v2_causes_and_durations
+AS
+SELECT
+  COALESCE(SUM(jank.dur), 0) / 1.0e6 AS scroll_jank_processing_ms,
+  COUNT(*) AS num_scroll_janks,
+  RepeatedField(
+    ChromeScrollJankV2_ScrollJankCauseAndDuration(
+      'cause', cause.cause_of_jank, 'duration_ms', jank.dur / 1.0e6))
+    AS scroll_jank_causes_and_durations
+FROM
+  scroll_event_latency_jank AS jank
+LEFT JOIN
+  event_latency_scroll_jank_cause AS cause
+  ON
+    jank.id = cause.slice_id
+WHERE
+  jank.jank AND cause.cause_of_jank != 'RendererCompositorQueueingDelay';
+
+DROP VIEW IF EXISTS __chrome_scroll_jank_v2;
+
+CREATE VIEW __chrome_scroll_jank_v2
+AS
+SELECT
+  100.0 * scroll_jank_processing_ms / scroll_processing_ms
+    AS scroll_jank_percentage,
+  *
+FROM
+  (
+    SELECT
+      total_scroll_processing.scroll_processing_ms,
+      causes_and_durations.scroll_jank_processing_ms,
+      causes_and_durations.num_scroll_janks,
+      causes_and_durations.scroll_jank_causes_and_durations
+    FROM
+      __chrome_scroll_jank_v2_scroll_processing
+        AS total_scroll_processing,
+      __chrome_scroll_jank_v2_causes_and_durations AS causes_and_durations
+  );
+
 DROP VIEW IF EXISTS chrome_scroll_jank_v2_output;
 
 CREATE VIEW chrome_scroll_jank_v2_output
@@ -29,33 +82,10 @@
     'scroll_jank_processing_ms',
     scroll_jank_processing_ms,
     'scroll_jank_percentage',
-    scroll_jank_percentage)
+    scroll_jank_percentage,
+    'num_scroll_janks',
+    num_scroll_janks,
+    'scroll_jank_causes_and_durations',
+    scroll_jank_causes_and_durations)
 FROM
-  (
-    SELECT
-      100.0 * scroll_jank_processing_ms / scroll_processing_ms
-        AS scroll_jank_percentage,
-      *
-    FROM
-      (
-        SELECT
-          COALESCE(SUM(jank.dur), 0) / 1.0e6 AS scroll_processing_ms,
-          COALESCE(
-            SUM(
-              CASE
-                WHEN
-                  jank.jank
-                  AND cause.cause_of_jank != 'RendererCompositorQueueingDelay'
-                  THEN jank.dur
-                ELSE 0
-                END),
-            0)
-            / 1.0e6 AS scroll_jank_processing_ms
-        FROM
-          scroll_event_latency_jank AS jank
-        LEFT JOIN
-          event_latency_scroll_jank_cause AS cause
-          ON
-            jank.id = cause.slice_id
-      )
-  );
+  __chrome_scroll_jank_v2;
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 f7f2cd7..ddbf904 100644
--- a/src/trace_processor/prelude/functions/BUILD.gn
+++ b/src/trace_processor/prelude/functions/BUILD.gn
@@ -29,10 +29,10 @@
     "import.h",
     "layout_functions.cc",
     "layout_functions.h",
+    "math.cc",
+    "math.h",
     "pprof_functions.cc",
     "pprof_functions.h",
-    "register_function.cc",
-    "register_function.h",
     "sqlite3_str_split.cc",
     "sqlite3_str_split.h",
     "stack_functions.cc",
@@ -59,7 +59,7 @@
     "../../importers/common",
     "../../importers/ftrace:ftrace_descriptors",
     "../../prelude/table_functions",
-    "../../sqlite:sqlite_minimal",
+    "../../sqlite",
     "../../storage",
     "../../types",
     "../../util",
@@ -67,6 +67,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") {
@@ -78,6 +92,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..9ca6ba8 100644
--- a/src/trace_processor/prelude/functions/create_function.cc
+++ b/src/trace_processor/prelude/functions/create_function.cc
@@ -19,7 +19,9 @@
 #include "perfetto/base/status.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/prelude/functions/create_function_internal.h"
+#include "src/trace_processor/sqlite/perfetto_sql_engine.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 +33,11 @@
 
 struct CreatedFunction : public SqlFunction {
   struct Context {
-    sqlite3* db;
+    PerfettoSqlEngine* engine;
     Prototype prototype;
     sql_argument::Type return_type;
     std::string sql;
-    sqlite3_stmt* stmt;
+    ScopedStmt stmt;
   };
 
   static base::Status Run(Context* ctx,
@@ -88,20 +90,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);
-  RETURN_IF_ERROR(
-      SqliteRetToStatus(ctx->db, ctx->prototype.function_name, ret));
+  int ret = sqlite3_step(ctx->stmt.get());
+  RETURN_IF_ERROR(SqliteRetToStatus(ctx->engine->sqlite_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 +112,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 +127,11 @@
 }
 
 base::Status CreatedFunction::VerifyPostConditions(Context* ctx) {
-  int ret = sqlite3_step(ctx->stmt);
-  RETURN_IF_ERROR(
-      SqliteRetToStatus(ctx->db, ctx->prototype.function_name, ret));
+  int ret = sqlite3_step(ctx->stmt.get());
+  RETURN_IF_ERROR(SqliteRetToStatus(ctx->engine->sqlite_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 +142,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(PerfettoSqlEngine* engine,
                                  size_t argc,
                                  sqlite3_value** argv,
                                  SqlValue&,
@@ -216,16 +212,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->sqlite_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,28 +246,28 @@
   // 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(),
-                               static_cast<int>(sql_defn_str.size()), &stmt_raw,
-                               nullptr);
+  int ret = sqlite3_prepare_v2(
+      engine->sqlite_engine()->db(), sql_defn_str.data(),
+      static_cast<int>(sql_defn_str.size()), &stmt_raw, nullptr);
   if (ret != SQLITE_OK) {
     return base::ErrStatus(
         "CREATE_FUNCTION[prototype=%s]: SQLite error when preparing "
         "statement %s",
         prototype_str.ToStdString().c_str(),
-        sqlite_utils::FormatErrorMessage(
-            stmt_raw, base::StringView(sql_defn_str), ctx->db, ret)
+        sqlite_utils::FormatErrorMessage(stmt_raw,
+                                         base::StringView(sql_defn_str),
+                                         engine->sqlite_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..ed8c06c 100644
--- a/src/trace_processor/prelude/functions/create_function.h
+++ b/src/trace_processor/prelude/functions/create_function.h
@@ -20,39 +20,20 @@
 #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 {
 
+class PerfettoSqlEngine;
+
 // Implementation of CREATE_FUNCTION SQL function.
 // See https://perfetto.dev/docs/analysis/metrics#metric-helper-functions for
 // usage of this function.
 struct CreateFunction : public SqlFunction {
-  struct PerFunctionState {
-    ScopedStmt stmt;
-    // 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 = PerfettoSqlEngine;
 
   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 9b7dab9..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;
 
     base::Status Filter(const QueryConstraints& qc,
                         sqlite3_value**,
-                        FilterHistory) override;
-    base::Status Next() override;
-    bool Eof() override;
-    base::Status Column(sqlite3_context* context, int N) override;
+                        FilterHistory);
+    base::Status Next();
+    bool Eof();
+    base::Status Column(sqlite3_context* context, int N);
 
    private:
     ScopedStmt scoped_stmt_;
@@ -57,19 +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) {
-    RegistrationFlags flags;
-    flags.type = RegistrationFlags::kExplicitCreateStateless;
-
-    SqliteTable::Register<CreatedViewFunction>(
-        db, nullptr, "internal_view_function_impl", flags);
-  }
+  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();
@@ -249,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));
 }
 
@@ -275,7 +269,7 @@
 }
 
 CreatedViewFunction::Cursor::Cursor(CreatedViewFunction* table)
-    : SqliteTable::Cursor(table), table_(table) {}
+    : SqliteTable::BaseCursor(table), table_(table) {}
 
 CreatedViewFunction::Cursor::~Cursor() = default;
 
@@ -488,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/math.cc b/src/trace_processor/prelude/functions/math.cc
new file mode 100644
index 0000000..14fc253
--- /dev/null
+++ b/src/trace_processor/prelude/functions/math.cc
@@ -0,0 +1,86 @@
+// 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/prelude/functions/math.h"
+
+#include <cmath>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/prelude/functions/sql_function.h"
+#include "src/trace_processor/sqlite/perfetto_sql_engine.h"
+#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_processor {
+
+namespace {
+
+struct Ln : public SqlFunction {
+  static base::Status Run(Context*,
+                          size_t argc,
+                          sqlite3_value** argv,
+                          SqlValue& out,
+                          Destructors&) {
+    PERFETTO_CHECK(argc == 1);
+    switch (sqlite3_value_numeric_type(argv[0])) {
+      case SQLITE_INTEGER:
+      case SQLITE_FLOAT: {
+        double value = sqlite3_value_double(argv[0]);
+        if (value > 0.0) {
+          out = SqlValue::Double(std::log(value));
+        }
+        break;
+      }
+      case SqlValue::kNull:
+      case SqlValue::kString:
+      case SqlValue::kBytes:
+        break;
+    }
+
+    return base::OkStatus();
+  }
+};
+
+struct Exp : public SqlFunction {
+  static base::Status Run(Context*,
+                          size_t argc,
+                          sqlite3_value** argv,
+                          SqlValue& out,
+                          Destructors&) {
+    PERFETTO_CHECK(argc == 1);
+    switch (sqlite3_value_numeric_type(argv[0])) {
+      case SQLITE_INTEGER:
+      case SQLITE_FLOAT:
+        out = SqlValue::Double(std::exp(sqlite3_value_double(argv[0])));
+        break;
+      case SqlValue::kNull:
+      case SqlValue::kString:
+      case SqlValue::kBytes:
+        break;
+    }
+
+    return base::OkStatus();
+  }
+};
+
+}  // namespace
+
+base::Status RegisterMathFunctions(PerfettoSqlEngine& engine) {
+  RETURN_IF_ERROR(engine.RegisterSqlFunction<Ln>("ln", 1, nullptr, true));
+  return engine.RegisterSqlFunction<Exp>("exp", 1, nullptr, true);
+}
+
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/prelude/functions/math.h b/src/trace_processor/prelude/functions/math.h
new file mode 100644
index 0000000..746ab44
--- /dev/null
+++ b/src/trace_processor/prelude/functions/math.h
@@ -0,0 +1,31 @@
+// 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_MATH_H_
+#define SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_MATH_H_
+
+#include "perfetto/base/status.h"
+
+namespace perfetto::trace_processor {
+
+class PerfettoSqlEngine;
+
+// Registers LN and EXP.
+// We do not compile the SQLite library with -DSQLITE_ENABLE_MATH_FUNCTIONS so
+// these functions are not provided by default.
+base::Status RegisterMathFunctions(PerfettoSqlEngine& engine);
+
+}  // namespace perfetto::trace_processor
+
+#endif  // SRC_TRACE_PROCESSOR_PRELUDE_FUNCTIONS_MATH_H_
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..b73afa0 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/perfetto_sql_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(PerfettoSqlEngine* 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..011361d 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 PerfettoSqlEngine;
 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(PerfettoSqlEngine* engine,
                                     TraceProcessorContext* context);
 
 }  // namespace trace_processor
diff --git a/src/trace_processor/prelude/functions/to_ftrace.h b/src/trace_processor/prelude/functions/to_ftrace.h
index c32ed79..9c55067 100644
--- a/src/trace_processor/prelude/functions/to_ftrace.h
+++ b/src/trace_processor/prelude/functions/to_ftrace.h
@@ -19,7 +19,7 @@
 
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/string_writer.h"
-#include "src/trace_processor/prelude/functions/register_function.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"
 
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 54e8999..2ee2839 100644
--- a/src/trace_processor/prelude/operators/span_join_operator.cc
+++ b/src/trace_processor/prelude/operators/span_join_operator.cc
@@ -106,20 +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) {
-  RegistrationFlags flags;
-  flags.type = RegistrationFlags::kExplicitCreateStateless;
-
-  SqliteTable::Register<SpanJoinOperatorTable>(db, storage, "span_join", flags);
-  SqliteTable::Register<SpanJoinOperatorTable>(db, storage, "span_left_join",
-                                               flags);
-  SqliteTable::Register<SpanJoinOperatorTable>(db, storage, "span_outer_join",
-                                               flags);
-}
+SpanJoinOperatorTable::~SpanJoinOperatorTable() = default;
 
 util::Status SpanJoinOperatorTable::Init(int argc,
                                          const char* const* argv,
@@ -239,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_));
 }
 
@@ -404,10 +393,11 @@
 }
 
 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::Filter(const QueryConstraints& qc,
                                                    sqlite3_value** argv,
diff --git a/src/trace_processor/prelude/operators/span_join_operator.h b/src/trace_processor/prelude/operators/span_join_operator.h
index e92aac2..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,17 +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;
 
     base::Status Filter(const QueryConstraints& qc,
                         sqlite3_value** argv,
-                        FilterHistory) override;
-    base::Status Next() override;
-    base::Status Column(sqlite3_context* context, int N) override;
-    bool Eof() override;
+                        FilterHistory);
+    base::Status Next();
+    base::Status Column(sqlite3_context* context, int N);
+    bool Eof();
 
    private:
     Cursor(Cursor&) = delete;
@@ -350,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 ddaff51..68f2ff4 100644
--- a/src/trace_processor/prelude/operators/window_operator.cc
+++ b/src/trace_processor/prelude/operators/window_operator.cc
@@ -27,15 +27,7 @@
 }  // namespace
 
 WindowOperatorTable::WindowOperatorTable(sqlite3*, const TraceStorage*) {}
-
-void WindowOperatorTable::RegisterTable(sqlite3* db,
-                                        const TraceStorage* storage) {
-  RegistrationFlags flags;
-  flags.writable = true;
-  flags.type = RegistrationFlags::kEponymous;
-
-  SqliteTable::Register<WindowOperatorTable>(db, storage, "window", flags);
-}
+WindowOperatorTable::~WindowOperatorTable() = default;
 
 base::Status WindowOperatorTable::Init(int,
                                        const char* const*,
@@ -62,8 +54,8 @@
   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*) {
@@ -105,7 +97,8 @@
 }
 
 WindowOperatorTable::Cursor::Cursor(WindowOperatorTable* table)
-    : SqliteTable::Cursor(table), table_(table) {}
+    : SqliteTable::BaseCursor(table), table_(table) {}
+WindowOperatorTable::Cursor::~Cursor() = default;
 
 base::Status WindowOperatorTable::Cursor::Filter(const QueryConstraints& qc,
                                                  sqlite3_value** argv,
diff --git a/src/trace_processor/prelude/operators/window_operator.h b/src/trace_processor/prelude/operators/window_operator.h
index c29fa4f..5f4af16 100644
--- a/src/trace_processor/prelude/operators/window_operator.h
+++ b/src/trace_processor/prelude/operators/window_operator.h
@@ -28,7 +28,8 @@
 
 class TraceStorage;
 
-class WindowOperatorTable : public SqliteTable {
+class WindowOperatorTable final
+    : public TypedSqliteTable<WindowOperatorTable, const TraceStorage*> {
  public:
   enum Column {
     kRowId = 0,
@@ -39,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.
     base::Status Filter(const QueryConstraints& qc,
                         sqlite3_value**,
-                        FilterHistory) override;
-    base::Status Next() override;
-    bool Eof() override;
-    base::Status Column(sqlite3_context*, int N) override;
+                        FilterHistory);
+    base::Status Next();
+    bool Eof();
+    base::Status Column(sqlite3_context*, int N);
 
    private:
     // Defines the data to be generated by the table.
@@ -73,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;
-  base::Status ModifyConstraints(QueryConstraints* qc) override;
-  base::Status 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;
@@ -92,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 1125c22..2a97bca 100644
--- a/src/trace_processor/prelude/table_functions/BUILD.gn
+++ b/src/trace_processor/prelude/table_functions/BUILD.gn
@@ -39,8 +39,6 @@
     "experimental_slice_layout.h",
     "flamegraph_construction_algorithms.cc",
     "flamegraph_construction_algorithms.h",
-    "table_function.cc",
-    "table_function.h",
     "view.cc",
     "view.h",
   ]
@@ -53,12 +51,26 @@
     "../../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") {
diff --git a/src/trace_processor/prelude/table_functions/tables.py b/src/trace_processor/prelude/table_functions/tables.py
index 4ffc526..32e4550 100644
--- a/src/trace_processor/prelude/table_functions/tables.py
+++ b/src/trace_processor/prelude/table_functions/tables.py
@@ -28,7 +28,7 @@
 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
+from src.trace_processor.tables.sched_tables import SCHED_SLICE_TABLE
 
 ANCESTOR_SLICE_TABLE = Table(
     python_module=__file__,
diff --git a/src/trace_processor/prelude/tables_views/tables.sql b/src/trace_processor/prelude/tables_views/tables.sql
index 1b7d016..28ea0f5 100644
--- a/src/trace_processor/prelude/tables_views/tables.sql
+++ b/src/trace_processor/prelude/tables_views/tables.sql
@@ -20,4 +20,6 @@
   ts BIGINT,
   dur BIGINT,
   depth BIGINT
-);
\ No newline at end of file
+);
+
+CREATE VIRTUAL TABLE window USING window();
diff --git a/src/trace_processor/sqlite/BUILD.gn b/src/trace_processor/sqlite/BUILD.gn
index 25024b1..5ba1283 100644
--- a/src/trace_processor/sqlite/BUILD.gn
+++ b/src/trace_processor/sqlite/BUILD.gn
@@ -20,49 +20,55 @@
   sources = [
     "db_sqlite_table.cc",
     "db_sqlite_table.h",
+    "perfetto_sql_engine.cc",
+    "perfetto_sql_engine.h",
+    "perfetto_sql_parser.cc",
+    "perfetto_sql_parser.h",
     "query_cache.h",
+    "scoped_db.h",
     "sql_stats_table.cc",
     "sql_stats_table.h",
     "sqlite_engine.cc",
     "sqlite_engine.h",
+    "sqlite_table.cc",
+    "sqlite_table.h",
+    "sqlite_tokenizer.cc",
+    "sqlite_tokenizer.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",
   ]
 }
@@ -71,12 +77,14 @@
   testonly = true
   sources = [
     "db_sqlite_table_unittest.cc",
+    "perfetto_sql_parser_unittest.cc",
     "query_constraints_unittest.cc",
+    "sqlite_tokenizer_unittest.cc",
     "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 8f2d066..f73c5c1 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -135,32 +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,
-                                                RegistrationFlags{});
-}
-
-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()));
-
-  std::string table_name = generator->TableName();
-  Context context{cache, TableComputation::kDynamic, nullptr,
-                  std::move(generator)};
-  RegistrationFlags flags;
-  flags.requires_hidden_constraints = !status.ok();
-  SqliteTable::Register<DbSqliteTable, Context>(db, std::move(context),
-                                                table_name, flags);
-}
-
 base::Status DbSqliteTable::Init(int, const char* const*, Schema* schema) {
   switch (computation_) {
     case TableComputation::kStatic:
@@ -395,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,
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 337385e..96a63e5 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -27,21 +27,37 @@
 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;
@@ -49,10 +65,10 @@
     // Implementation of SqliteTable::Cursor.
     base::Status Filter(const QueryConstraints& qc,
                         sqlite3_value** argv,
-                        FilterHistory) override;
-    base::Status Next() override;
-    bool Eof() override;
-    base::Status Column(sqlite3_context*, int N) override;
+                        FilterHistory);
+    base::Status Next();
+    bool Eof();
+    base::Status Column(sqlite3_context*, int N);
 
    private:
     enum class Mode {
@@ -109,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;
-  base::Status 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/perfetto_sql_engine.cc b/src/trace_processor/sqlite/perfetto_sql_engine.cc
new file mode 100644
index 0000000..ea06188
--- /dev/null
+++ b/src/trace_processor/sqlite/perfetto_sql_engine.cc
@@ -0,0 +1,194 @@
+/*
+ * 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/perfetto_sql_engine.h"
+
+#include "src/trace_processor/sqlite/db_sqlite_table.h"
+#include "src/trace_processor/tp_metatrace.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+void IncrementCountForStmt(sqlite3_stmt* stmt,
+                           PerfettoSqlEngine::ExecutionResult* res) {
+  res->statement_count++;
+
+  // If the stmt is already done, it clearly didn't have any output.
+  if (sqlite_utils::IsStmtDone(stmt))
+    return;
+
+  if (sqlite3_column_count(stmt) == 1) {
+    sqlite3_value* value = sqlite3_column_value(stmt, 0);
+
+    // If the "VOID" pointer associated to the return value is not null,
+    // that means this is a function which is forced to return a value
+    // (because all functions in SQLite have to) but doesn't actually
+    // wait to (i.e. it wants to be treated like CREATE TABLE or similar).
+    // Because of this, ignore the return value of this function.
+    // See |WrapSqlFunction| for where this is set.
+    if (sqlite3_value_pointer(value, "VOID") != nullptr) {
+      return;
+    }
+
+    // If the statement only has a single column and that column is named
+    // "suppress_query_output", treat it as a statement without output for
+    // accounting purposes. This allows an escape hatch for cases where the
+    // user explicitly wants to ignore functions as having output.
+    if (strcmp(sqlite3_column_name(stmt, 0), "suppress_query_output") == 0) {
+      return;
+    }
+  }
+
+  // Otherwise, the statement has output and so increment the count.
+  res->statement_count_with_output++;
+}
+
+}  // namespace
+
+PerfettoSqlEngine::PerfettoSqlEngine() : query_cache_(new QueryCache()) {}
+
+void PerfettoSqlEngine::RegisterTable(const Table& table,
+                                      const std::string& table_name) {
+  DbSqliteTable::Context context{query_cache_.get(),
+                                 DbSqliteTable::TableComputation::kStatic,
+                                 &table, nullptr};
+  engine_.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(engine_.db(), 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 PerfettoSqlEngine::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)};
+  engine_.RegisterVirtualTableModule<DbSqliteTable>(
+      table_name, std::move(context), SqliteTable::kEponymousOnly, false);
+}
+
+base::StatusOr<PerfettoSqlEngine::ExecutionResult>
+PerfettoSqlEngine::ExecuteUntilLastStatement(const std::string& sql) {
+  ExecutionResult res;
+
+  // A sql string can contain several statements. Some of them might be comment
+  // only, e.g. "SELECT 1; /* comment */; SELECT 2;". Here we process one
+  // statement on each iteration. SQLite's sqlite_prepare_v2 (wrapped by
+  // PrepareStmt) returns on each iteration a pointer to the unprocessed string.
+  //
+  // Unfortunately we cannot call PrepareStmt and tokenize all statements
+  // upfront because sqlite_prepare_v2 also semantically checks the statement
+  // against the schema. In some cases statements might depend on the execution
+  // of previous ones (e.e. CREATE VIEW x; SELECT FROM x; DELETE VIEW x;).
+  //
+  // Also, unfortunately, we need to PrepareStmt to find out if a statement is a
+  // comment or a real statement.
+  //
+  // The logic here is the following:
+  //  - We invoke PrepareStmt on each statement.
+  //  - If the statement is a comment we simply skip it.
+  //  - If the statement is valid, we step once to make sure side effects take
+  //    effect.
+  //  - If we encounter a valid statement afterwards, we step internally through
+  //    all rows of the previous one. This ensures that any further side effects
+  //    take hold *before* we step into the next statement.
+  //  - Once no further non-comment statements are encountered, we return an
+  //    iterator to the last valid statement.
+  for (const char* rem_sql = sql.c_str(); rem_sql && rem_sql[0];) {
+    ScopedStmt cur_stmt;
+    {
+      PERFETTO_TP_TRACE(metatrace::Category::QUERY, "QUERY_PREPARE");
+      const char* tail = nullptr;
+      RETURN_IF_ERROR(
+          sqlite_utils::PrepareStmt(engine_.db(), rem_sql, &cur_stmt, &tail));
+      rem_sql = tail;
+    }
+
+    // The only situation where we'd have an ok status but also no prepared
+    // statement is if the statement of SQL we parsed was a pure comment. In
+    // this case, just continue to the next statement.
+    if (!cur_stmt)
+      continue;
+
+    // Before stepping into |cur_stmt|, we need to finish iterating through
+    // the previous statement so we don't have two clashing statements (e.g.
+    // SELECT * FROM v and DROP VIEW v) partially stepped into.
+    if (res.stmt) {
+      PERFETTO_TP_TRACE(metatrace::Category::QUERY, "STMT_STEP_UNTIL_DONE",
+                        [&res](metatrace::Record* record) {
+                          auto expanded_sql =
+                              sqlite_utils::ExpandedSqlForStmt(res.stmt.get());
+                          record->AddArg("SQL", expanded_sql.get());
+                        });
+      RETURN_IF_ERROR(sqlite_utils::StepStmtUntilDone(res.stmt.get()));
+      res.stmt.reset();
+    }
+
+    PERFETTO_DLOG("Executing statement: %s", sqlite3_sql(*cur_stmt));
+
+    {
+      PERFETTO_TP_TRACE(metatrace::Category::TOPLEVEL, "STMT_FIRST_STEP",
+                        [&cur_stmt](metatrace::Record* record) {
+                          auto expanded_sql =
+                              sqlite_utils::ExpandedSqlForStmt(*cur_stmt);
+                          record->AddArg("SQL", expanded_sql.get());
+                        });
+
+      // Now step once into |cur_stmt| so that when we prepare the next statment
+      // we will have executed any dependent bytecode in this one.
+      int err = sqlite3_step(*cur_stmt);
+      if (err != SQLITE_ROW && err != SQLITE_DONE) {
+        return base::ErrStatus(
+            "%s", sqlite_utils::FormatErrorMessage(
+                      cur_stmt.get(), base::StringView(sql), engine_.db(), err)
+                      .c_message());
+      }
+    }
+
+    // Increment the neecessary counts for the statement.
+    IncrementCountForStmt(cur_stmt.get(), &res);
+
+    // Propogate the current statement to the next iteration.
+    res.stmt = std::move(cur_stmt);
+  }
+
+  // If we didn't manage to prepare a single statment, that means everything
+  // in the SQL was treated as a comment.
+  if (!res.stmt)
+    return base::ErrStatus("No valid SQL to run");
+
+  // Update the output statment and column count.
+  res.column_count =
+      static_cast<uint32_t>(sqlite3_column_count(res.stmt.get()));
+  return std::move(res);
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/sqlite/perfetto_sql_engine.h b/src/trace_processor/sqlite/perfetto_sql_engine.h
new file mode 100644
index 0000000..f06b159
--- /dev/null
+++ b/src/trace_processor/sqlite/perfetto_sql_engine.h
@@ -0,0 +1,184 @@
+/*
+ * 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_PERFETTO_SQL_ENGINE_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_PERFETTO_SQL_ENGINE_H_
+
+#include "perfetto/ext/base/status_or.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"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Intermediary class which translates high-level concepts and algorithms used
+// in trace processor into lower-level concepts and functions can be understood
+// by and executed against SQLite.
+class PerfettoSqlEngine {
+ public:
+  struct ExecutionResult {
+    ScopedStmt stmt;
+    uint32_t column_count = 0;
+    uint32_t statement_count = 0;
+    uint32_t statement_count_with_output = 0;
+  };
+
+  PerfettoSqlEngine();
+
+  // Executes all the statements in |sql| until the last one and returns a
+  // |ExecutionResult| object containing a |ScopedStmt| for the final statement
+  // and metadata about all statements executed.
+  //
+  // Returns an error if the execution of any statement failed or if there was
+  // no valid SQL to run.
+  base::StatusOr<ExecutionResult> ExecuteUntilLastStatement(
+      const std::string& sql);
+
+  // Registers a trace processor C++ function to be runnable from SQL.
+  //
+  // The format of the function is given by the |SqlFunction|.
+  //
+  // |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);
+  //                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 with SQLite with an SQL name of
+  // |name|.
+  void RegisterTable(const Table& table, const std::string& name);
+
+  // Registers a trace processor C++ table function with SQLite.
+  void RegisterTableFunction(std::unique_ptr<TableFunction> fn);
+
+  SqliteEngine* sqlite_engine() { return &engine_; }
+
+ private:
+  std::unique_ptr<QueryCache> query_cache_;
+  SqliteEngine engine_;
+};
+
+}  // 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 perfetto_sql_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 perfetto_sql_internal
+
+template <typename Function>
+base::Status PerfettoSqlEngine::RegisterSqlFunction(
+    const char* name,
+    int argc,
+    typename Function::Context* ctx,
+    bool deterministic) {
+  return engine_.RegisterFunction(
+      name, argc, perfetto_sql_internal::WrapSqlFunction<Function>, ctx,
+      nullptr, deterministic);
+}
+
+template <typename Function>
+base::Status PerfettoSqlEngine::RegisterSqlFunction(
+    const char* name,
+    int argc,
+    std::unique_ptr<typename Function::Context> user_data,
+    bool deterministic) {
+  auto ctx_destructor = [](void* ptr) {
+    delete static_cast<typename Function::Context*>(ptr);
+  };
+  return engine_.RegisterFunction(
+      name, argc, perfetto_sql_internal::WrapSqlFunction<Function>,
+      user_data.release(), ctx_destructor, deterministic);
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_SQLITE_PERFETTO_SQL_ENGINE_H_
diff --git a/src/trace_processor/sqlite/perfetto_sql_parser.cc b/src/trace_processor/sqlite/perfetto_sql_parser.cc
new file mode 100644
index 0000000..fb14368
--- /dev/null
+++ b/src/trace_processor/sqlite/perfetto_sql_parser.cc
@@ -0,0 +1,79 @@
+/*
+ * 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/perfetto_sql_parser.h"
+#include "perfetto/base/logging.h"
+#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using Token = SqliteTokenizer::Token;
+using Statement = PerfettoSqlParser::Statement;
+
+bool TokenIsTerminal(Token t) {
+  return t.token_type == SqliteTokenType::TK_SEMI || t.str.empty();
+}
+
+}  // namespace
+
+PerfettoSqlParser::PerfettoSqlParser(const char* sql)
+    : tokenizer_(sql), start_(sql) {}
+
+bool PerfettoSqlParser::Next() {
+  PERFETTO_DCHECK(status_.ok());
+
+  const char* non_space_ptr = nullptr;
+  for (Token token = tokenizer_.Next();; token = tokenizer_.Next()) {
+    // Space should always be completely ignored by any logic below as it will
+    // never change the current state in the state machine.
+    if (token.token_type == SqliteTokenType::TK_SPACE) {
+      continue;
+    }
+
+    if (TokenIsTerminal(token)) {
+      // If we have a non-space character we've seen, just return all the stuff
+      // we've seen between that and the current token.
+      if (non_space_ptr) {
+        uint32_t offset_of_non_space =
+            static_cast<uint32_t>(non_space_ptr - start_);
+        uint32_t chars_since_non_space =
+            static_cast<uint32_t>(tokenizer_.ptr() - non_space_ptr);
+        statement_ = Statement(
+            SqliteSql{std::string_view(non_space_ptr, chars_since_non_space),
+                      offset_of_non_space});
+        return true;
+      }
+      // This means we've seen a semi-colon without any non-space content. Just
+      // try and find the next statement as this "statement" is a noop.
+      if (token.token_type == SqliteTokenType::TK_SEMI) {
+        continue;
+      }
+      // This means we've reached the end of the SQL.
+      PERFETTO_DCHECK(token.str.empty());
+      return false;
+    }
+
+    // If we've not seen a space character, keep track of the current position.
+    if (!non_space_ptr) {
+      non_space_ptr = token.str.data();
+    }
+  }
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/sqlite/perfetto_sql_parser.h b/src/trace_processor/sqlite/perfetto_sql_parser.h
new file mode 100644
index 0000000..85622bb
--- /dev/null
+++ b/src/trace_processor/sqlite/perfetto_sql_parser.h
@@ -0,0 +1,86 @@
+/*
+ * 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_PERFETTO_SQL_PARSER_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_PERFETTO_SQL_PARSER_H_
+
+#include <string_view>
+#include <variant>
+
+#include "perfetto/ext/base/status_or.h"
+#include "src/trace_processor/sqlite/sqlite_tokenizer.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// Parser for PerfettoSQL statements. This class provides an iterator-style
+// interface for reading all PerfettoSQL statements from a block of SQL.
+//
+// Usage:
+// PerfettoSqlParser parser(my_sql_string.c_str());
+// while (parser.Next()) {
+//   auto& stmt = parser.statement();
+//   // Handle |stmt| here
+// }
+// RETURN_IF_ERROR(r.status());
+class PerfettoSqlParser {
+ public:
+  // Indicates that the specified SQLite SQL was extracted directly from a
+  // PerfettoSQL statement and should be directly executed with SQLite.
+  struct SqliteSql {
+    std::string_view sql;
+    uint32_t global_pos;
+
+    bool operator==(const SqliteSql& o) const {
+      return sql == o.sql && global_pos == o.global_pos;
+    }
+  };
+  using Statement = std::variant<SqliteSql>;
+
+  // Creates a new SQL parser with the a block of PerfettoSQL statements.
+  // Concretely, the passed string can contain >1 statement.
+  explicit PerfettoSqlParser(const char* sql);
+
+  // Attempts to parse to the next statement in the SQL. Returns true if
+  // a statement was successfully parsed and false if EOF was reached or the
+  // statement was not parsed correctly.
+  //
+  // Note: if this function returns false, callers *must* call |status()|: it
+  // is undefined behaviour to not do so.
+  bool Next();
+
+  // Returns the current statement which was parsed. This function *must not* be
+  // called unless |Next()| returned true.
+  Statement& statement() {
+    PERFETTO_DCHECK(statement_.has_value());
+    return statement_.value();
+  }
+
+  // Returns the error status for the parser. This will be |base::OkStatus()|
+  // until
+  const base::Status& status() const { return status_; }
+
+ private:
+  SqliteTokenizer tokenizer_;
+  const char* start_ = nullptr;
+  base::Status status_;
+  std::optional<Statement> statement_;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_SQLITE_PERFETTO_SQL_PARSER_H_
diff --git a/src/trace_processor/sqlite/perfetto_sql_parser_unittest.cc b/src/trace_processor/sqlite/perfetto_sql_parser_unittest.cc
new file mode 100644
index 0000000..014b5b2
--- /dev/null
+++ b/src/trace_processor/sqlite/perfetto_sql_parser_unittest.cc
@@ -0,0 +1,73 @@
+/*
+ * 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/perfetto_sql_parser.h"
+
+#include <variant>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/status_or.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using Result = PerfettoSqlParser::Statement;
+using SqliteSql = PerfettoSqlParser::SqliteSql;
+
+class PerfettoSqlParserTest : public ::testing::Test {
+ protected:
+  base::StatusOr<std::vector<PerfettoSqlParser::Statement>> Parse(
+      const char* sql) {
+    PerfettoSqlParser parser(sql);
+    std::vector<PerfettoSqlParser::Statement> results;
+    while (parser.Next()) {
+      results.push_back(std::move(parser.statement()));
+    }
+    if (!parser.status().ok()) {
+      return parser.status();
+    }
+    return results;
+  }
+};
+
+TEST_F(PerfettoSqlParserTest, Empty) {
+  ASSERT_THAT(*Parse(""), testing::IsEmpty());
+}
+
+TEST_F(PerfettoSqlParserTest, SemiColonTerminatedStatement) {
+  static constexpr char kSql[] = "SELECT * FROM slice;";
+  ASSERT_THAT(*Parse(kSql), testing::ElementsAre(SqliteSql{kSql, 0}));
+}
+
+TEST_F(PerfettoSqlParserTest, MultipleStmts) {
+  static constexpr char kSql[] = "SELECT * FROM slice; SELECT * FROM s";
+  ASSERT_THAT(*Parse(kSql),
+              testing::ElementsAre(SqliteSql{"SELECT * FROM slice;", 0},
+                                   SqliteSql{"SELECT * FROM s", 21}));
+}
+
+TEST_F(PerfettoSqlParserTest, IgnoreOnlySpace) {
+  static constexpr char kSql[] = " ; SELECT * FROM s; ; ;";
+  ASSERT_THAT(*Parse(kSql),
+              testing::ElementsAre(SqliteSql{"SELECT * FROM s;", 3}));
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
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 ad41857..e6cdf87 100644
--- a/src/trace_processor/sqlite/sql_stats_table.cc
+++ b/src/trace_processor/sqlite/sql_stats_table.cc
@@ -31,11 +31,7 @@
 
 SqlStatsTable::SqlStatsTable(sqlite3*, const TraceStorage* storage)
     : storage_(storage) {}
-
-void SqlStatsTable::RegisterTable(sqlite3* db, const TraceStorage* storage) {
-  SqliteTable::Register<SqlStatsTable>(db, storage, "sqlstats",
-                                       RegistrationFlags{});
-}
+SqlStatsTable::~SqlStatsTable() = default;
 
 base::Status SqlStatsTable::Init(int, const char* const*, Schema* schema) {
   *schema = Schema(
@@ -52,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*) {
@@ -61,8 +57,9 @@
 }
 
 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;
 
 base::Status SqlStatsTable::Cursor::Filter(const QueryConstraints&,
diff --git a/src/trace_processor/sqlite/sql_stats_table.h b/src/trace_processor/sqlite/sql_stats_table.h
index ad43234..d78224c 100644
--- a/src/trace_processor/sqlite/sql_stats_table.h
+++ b/src/trace_processor/sqlite/sql_stats_table.h
@@ -31,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,
@@ -41,18 +42,18 @@
   };
 
   // Implementation of the SQLite cursor interface.
-  class Cursor : public SqliteTable::Cursor {
+  class Cursor final : public SqliteTable::BaseCursor {
    public:
     explicit Cursor(SqlStatsTable* storage);
-    ~Cursor() override;
+    ~Cursor() final;
 
     // Implementation of SqliteTable::Cursor.
     base::Status Filter(const QueryConstraints&,
                         sqlite3_value**,
-                        FilterHistory) override;
-    base::Status Next() override;
-    bool Eof() override;
-    base::Status Column(sqlite3_context*, int N) override;
+                        FilterHistory);
+    base::Status Next();
+    bool Eof();
+    base::Status Column(sqlite3_context*, int N);
 
    private:
     Cursor(Cursor&) = delete;
@@ -68,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
index 429318a..8414193 100644
--- a/src/trace_processor/sqlite/sqlite_engine.cc
+++ b/src/trace_processor/sqlite/sqlite_engine.cc
@@ -15,8 +15,13 @@
  */
 
 #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.
@@ -58,7 +63,7 @@
 
 }  // namespace
 
-SqliteEngine::SqliteEngine() : query_cache_(new QueryCache()) {
+SqliteEngine::SqliteEngine() {
   sqlite3* db = nullptr;
   EnsureSqliteInitialized();
   PERFETTO_CHECK(sqlite3_open(":memory:", &db) == SQLITE_OK);
@@ -66,12 +71,69 @@
   db_.reset(std::move(db));
 }
 
-void SqliteEngine::RegisterTable(const Table& table, const std::string& name) {
-  DbSqliteTable::RegisterTable(*db_, query_cache_.get(), &table, name);
+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::RegisterTableFunction(std::unique_ptr<TableFunction> fn) {
-  DbSqliteTable::RegisterTable(*db_, query_cache_.get(), std::move(fn));
+base::Status SqliteEngine::RegisterFunction(const char* name,
+                                            int argc,
+                                            Fn* fn,
+                                            void* ctx,
+                                            FnCtxDestructor* destructor,
+                                            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, fn, nullptr, nullptr, destructor);
+  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();
+}
+
+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
diff --git a/src/trace_processor/sqlite/sqlite_engine.h b/src/trace_processor/sqlite/sqlite_engine.h
index f1ebdd3..6c5ad10 100644
--- a/src/trace_processor/sqlite/sqlite_engine.h
+++ b/src/trace_processor/sqlite/sqlite_engine.h
@@ -18,11 +18,21 @@
 #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 {
@@ -37,25 +47,87 @@
 //    what functionality we rely on.
 class SqliteEngine {
  public:
+  using Fn = void(sqlite3_context* ctx, int argc, sqlite3_value** argv);
+  using FnCtxDestructor = void(void*);
+
   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 C++ function to be runnable from SQL.
+  base::Status RegisterFunction(const char* name,
+                                int argc,
+                                Fn* fn,
+                                void* ctx,
+                                FnCtxDestructor* ctx_destructor,
+                                bool deterministic);
 
-  // Registers a trace processor C++ function with SQLite.
-  void RegisterTableFunction(std::unique_ptr<TableFunction> fn);
+  // Registers a SQLite virtual table module with the given name.
+  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.
+  base::Status DeclareVirtualTable(const std::string& create_stmt);
+
+  // Saves a SQLite table across a pair of xDisconnect/xConnect callbacks.
+  base::Status SaveSqliteTable(const std::string& table_name,
+                               std::unique_ptr<SqliteTable>);
+
+  // Restores a SQLite table across a pair of xDisconnect/xConnect callbacks.
+  base::StatusOr<std::unique_ptr<SqliteTable>> RestoreSqliteTable(
+      const std::string& table_name);
+
+  // Gets the context for a registered SQL function.
+  void* GetFunctionContext(const std::string& name, int argc);
 
   sqlite3* db() const { return db_.get(); }
 
  private:
-  // Keep this first: we need this to be destroyed after we clean up
-  // everything else.
+  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());
+    }
+  };
+
+  base::FlatHashMap<std::string, std::unique_ptr<SqliteTable>> saved_tables_;
+  base::FlatHashMap<std::pair<std::string, int>, void*, FnHasher> fn_ctx_;
+
   ScopedDb db_;
-  std::unique_ptr<QueryCache> query_cache_;
 };
 
 }  // 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 {
+
+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 343ae04..2ad925c 100644
--- a/src/trace_processor/sqlite/sqlite_table.cc
+++ b/src/trace_processor/sqlite/sqlite_table.cc
@@ -20,10 +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 {
@@ -154,88 +160,6 @@
 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 = SetStatusAndReturn(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;
-}
-
 base::Status SqliteTable::ModifyConstraints(QueryConstraints*) {
   return base::OkStatus();
 }
@@ -245,7 +169,7 @@
 }
 
 base::Status SqliteTable::Update(int, sqlite3_value**, sqlite3_int64*) {
-  return base::OkStatus();
+  return base::ErrStatus("Updating not supported");
 }
 
 bool SqliteTable::ReadConstraints(int idxNum, const char* idxStr, int argc) {
@@ -275,12 +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;
+
+////////////////////////////////////////////////////////////////////////////////
+// SqliteTable::Column implementation
+////////////////////////////////////////////////////////////////////////////////
 
 SqliteTable::Column::Column(size_t index,
                             std::string name,
@@ -288,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)) {
@@ -299,7 +237,6 @@
   }
 }
 
-SqliteTable::Schema::Schema() = default;
 SqliteTable::Schema::Schema(const Schema&) = default;
 SqliteTable::Schema& SqliteTable::Schema::operator=(const Schema&) = default;
 
@@ -331,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 2d2599b..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,40 +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 base::Status 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 base::Status Next() = 0;
+    base::Status Next();
 
     // Called to check if the cursor has reached eof. Column will be called iff
     // this method returns true.
-    virtual bool Eof() = 0;
+    bool Eof();
 
     // Used to extract the value from the column at index |N|.
-    virtual base::Status Column(sqlite3_context* context, int N) = 0;
+    base::Status Column(sqlite3_context* context, int N);
+
+    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;
-
-    int SetStatusAndReturn(base::Status status) {
-      return table_->SetStatusAndReturn(status);
-    }
-
     SqliteTable* table_ = nullptr;
   };
 
@@ -160,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.
@@ -186,194 +196,11 @@
     int64_t estimated_rows = 0;
   };
 
-  struct RegistrationFlags {
-    // Specifies whether the table can also be written to.
-    bool writable = false;
-
-    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 automatically exists in the main schema and can also be
-      // created with CREATE VIRTUAL TABLE.
-      // Note: the name value here matches the naming in the vtable docs of
-      // SQLite.
-      kEponymous,
-
-      // A table which must be explicitly created using a CREATE VIRTUAL TABLE
-      // statement (i.e. does exist automatically) but does not have any
-      // backing state beyond the arguments passed to it.
-      kExplicitCreateStateless,
-    };
-    TableType type = TableType::kEponymousOnly;
-
-    // Whether the table requires some number of hidden constraints to be passed
-    // to be able to the queried (i.e. a SELECT * FROM table would not work).
-    bool requires_hidden_constraints = false;
-  };
-
   SqliteTable();
 
-  // Called by derived classes to register themselves with the SQLite db.
-  // 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,
-                       RegistrationFlags flags) {
-    using TCursor = typename TTable::Cursor;
-
-    struct TableDescriptor {
-      SqliteTable::Factory<Context> factory;
-      Context context;
-      sqlite3_module module = {};
-    };
-
-    std::unique_ptr<TableDescriptor> desc(new TableDescriptor());
-    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*>(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;
-    };
-
-    switch (flags.type) {
-      case RegistrationFlags::kEponymousOnly:
-        module->xCreate = nullptr;
-        break;
-      case RegistrationFlags::kEponymous:
-        module->xCreate = create_fn;
-        break;
-      case RegistrationFlags::kExplicitCreateStateless:
-        // TODO(lalitm): this is not accurate as we're basically creating an
-        // eponymous table. Change this to be a different function once we can
-        // do so easily.
-        module->xCreate = create_fn;
-        break;
-    }
-    module->xConnect = create_fn;
-    module->xDisconnect = destroy_fn;
-    module->xDestroy = destroy_fn;
-    module->xOpen = [](sqlite3_vtab* t, sqlite3_vtab_cursor** c) {
-      return static_cast<SqliteTable*>(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) {
-      bool is_cached =
-          static_cast<Cursor*>(vc)->table_->ReadConstraints(i, s, a);
-
-      auto history = is_cached ? Cursor::FilterHistory::kSame
-                               : Cursor::FilterHistory::kDifferent;
-      auto* cursor = static_cast<TCursor*>(vc);
-      return cursor->SetStatusAndReturn(cursor->Filter(
-          static_cast<Cursor*>(vc)->table_->qc_cache_, v, history));
-    };
-    module->xNext = [](sqlite3_vtab_cursor* c) {
-      auto* cursor = static_cast<TCursor*>(c);
-      return cursor->SetStatusAndReturn(cursor->Next());
-    };
-    module->xEof = [](sqlite3_vtab_cursor* c) {
-      return static_cast<int>(static_cast<TCursor*>(c)->Eof());
-    };
-    module->xColumn = [](sqlite3_vtab_cursor* c, sqlite3_context* a, int b) {
-      auto* cursor = static_cast<TCursor*>(c);
-      return cursor->SetStatusAndReturn(cursor->Column(a, b));
-    };
-    module->xRowid = [](sqlite3_vtab_cursor*, sqlite3_int64*) {
-      return SQLITE_ERROR;
-    };
-    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 (flags.writable) {
-      module->xUpdate = [](sqlite3_vtab* t, int a, sqlite3_value** v,
-                           sqlite3_int64* r) {
-        auto* table = static_cast<TTable*>(t);
-        return table->SetStatusAndReturn(table->Update(a, v, r));
-      };
-    }
-
-    int res = sqlite3_create_module_v2(
-        db, module_name.c_str(), module, desc.release(),
-        [](void* arg) { delete static_cast<TableDescriptor*>(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 which requires explicit CREATE statements or require
-    // hidden constraints cannot be inserted.
-    bool explicit_create =
-        flags.type == RegistrationFlags::kExplicitCreateStateless;
-    if (!explicit_create && !flags.requires_hidden_constraints) {
-      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.
@@ -384,36 +211,24 @@
   // At registration time, the function should also pass true for |read_write|.
   virtual base::Status Update(int, sqlite3_value**, sqlite3_int64*);
 
+  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*);
-
-  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, 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
@@ -432,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/sqlite_tokenizer.cc b/src/trace_processor/sqlite/sqlite_tokenizer.cc
new file mode 100644
index 0000000..1766baa
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_tokenizer.cc
@@ -0,0 +1,448 @@
+/*
+ * 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_tokenizer.h"
+
+#include <ctype.h>
+#include <sqlite3.h>
+#include <optional>
+#include <string_view>
+
+#include "perfetto/base/compiler.h"
+
+namespace perfetto {
+namespace trace_processor {
+
+// The contents of this file are ~copied from SQLite with some modifications to
+// minimize the amount copied: i.e. if we can call a libc function/public SQLite
+// API instead of a private one.
+//
+// The changes are as follows:
+// 1. Remove all ifdefs to only keep branches we actually use
+// 2. Change handling of |CC_KYWD0| to remove distinction between different
+//    SQLite kewords, reducing how many things we need to copy over.
+// 3. Constants are changed from be macro defines to be values in
+//    |SqliteTokenType|.
+
+namespace {
+
+const unsigned char sqlite3CtypeMap[256] = {
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00..07    ........ */
+    0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, /* 08..0f    ........ */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 10..17    ........ */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 18..1f    ........ */
+    0x01, 0x00, 0x80, 0x00, 0x40, 0x00, 0x00, 0x80, /* 20..27     !"#$%&' */
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 28..2f    ()*+,-./ */
+    0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, /* 30..37    01234567 */
+    0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 38..3f    89:;<=>? */
+
+    0x00, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x02, /* 40..47    @ABCDEFG */
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 48..4f    HIJKLMNO */
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, /* 50..57    PQRSTUVW */
+    0x02, 0x02, 0x02, 0x80, 0x00, 0x00, 0x00, 0x40, /* 58..5f    XYZ[\]^_ */
+    0x80, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x22, /* 60..67    `abcdefg */
+    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 68..6f    hijklmno */
+    0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, /* 70..77    pqrstuvw */
+    0x22, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, /* 78..7f    xyz{|}~. */
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 80..87    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 88..8f    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 90..97    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* 98..9f    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a0..a7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* a8..af    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b0..b7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* b8..bf    ........ */
+
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c0..c7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* c8..cf    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d0..d7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* d8..df    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e0..e7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* e8..ef    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* f0..f7    ........ */
+    0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40  /* f8..ff    ........ */
+};
+
+#define CC_X 0        /* The letter 'x', or start of BLOB literal */
+#define CC_KYWD0 1    /* First letter of a keyword */
+#define CC_KYWD 2     /* Alphabetics or '_'.  Usable in a keyword */
+#define CC_DIGIT 3    /* Digits */
+#define CC_DOLLAR 4   /* '$' */
+#define CC_VARALPHA 5 /* '@', '#', ':'.  Alphabetic SQL variables */
+#define CC_VARNUM 6   /* '?'.  Numeric SQL variables */
+#define CC_SPACE 7    /* Space characters */
+#define CC_QUOTE 8    /* '"', '\'', or '`'.  String literals, quoted ids */
+#define CC_QUOTE2 9   /* '['.   [...] style quoted ids */
+#define CC_PIPE 10    /* '|'.   Bitwise OR or concatenate */
+#define CC_MINUS 11   /* '-'.  Minus or SQL-style comment */
+#define CC_LT 12      /* '<'.  Part of < or <= or <> */
+#define CC_GT 13      /* '>'.  Part of > or >= */
+#define CC_EQ 14      /* '='.  Part of = or == */
+#define CC_BANG 15    /* '!'.  Part of != */
+#define CC_SLASH 16   /* '/'.  / or c-style comment */
+#define CC_LP 17      /* '(' */
+#define CC_RP 18      /* ')' */
+#define CC_SEMI 19    /* ';' */
+#define CC_PLUS 20    /* '+' */
+#define CC_STAR 21    /* '*' */
+#define CC_PERCENT 22 /* '%' */
+#define CC_COMMA 23   /* ',' */
+#define CC_AND 24     /* '&' */
+#define CC_TILDA 25   /* '~' */
+#define CC_DOT 26     /* '.' */
+#define CC_ID 27      /* unicode characters usable in IDs */
+#define CC_NUL 29     /* 0x00 */
+#define CC_BOM 30     /* First byte of UTF8 BOM:  0xEF 0xBB 0xBF */
+
+// clang-format off
+static const unsigned char aiClass[] = {
+/*         x0  x1  x2  x3  x4  x5  x6  x7  x8  x9  xa  xb  xc  xd  xe  xf */
+/* 0x */   29, 28, 28, 28, 28, 28, 28, 28, 28,  7,  7, 28,  7,  7, 28, 28,
+/* 1x */   28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+/* 2x */    7, 15,  8,  5,  4, 22, 24,  8, 17, 18, 21, 20, 23, 11, 26, 16,
+/* 3x */    3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  5, 19, 12, 14, 13,  6,
+/* 4x */    5,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+/* 5x */    1,  1,  1,  1,  1,  1,  1,  1,  0,  2,  2,  9, 28, 28, 28,  2,
+/* 6x */    8,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+/* 7x */    1,  1,  1,  1,  1,  1,  1,  1,  0,  2,  2, 28, 10, 28, 25, 28,
+/* 8x */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* 9x */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Ax */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Bx */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Cx */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Dx */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+/* Ex */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 30,
+/* Fx */   27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27
+};
+// clang-format on
+
+#define IdChar(C) ((sqlite3CtypeMap[static_cast<unsigned char>(C)] & 0x46) != 0)
+
+// Copy of |sqlite3GetToken| for use by the PerfettoSql transpiler.
+//
+// We copy this function because |sqlite3GetToken| is static to sqlite3.c
+// in most distributions of SQLite so we cannot call it from our code.
+//
+// While we could redefine SQLITE_PRIVATE, pragmatically that will not fly in
+// all the places we build trace processor so we need to resort to making a
+// copy.
+int GetSqliteToken(const unsigned char* z, SqliteTokenType* tokenType) {
+  int i, c;
+  switch (aiClass[*z]) { /* Switch on the character-class of the first byte
+                         ** of the token. See the comment on the CC_ defines
+                         ** above. */
+    case CC_SPACE: {
+      for (i = 1; isspace(z[i]); i++) {
+      }
+      *tokenType = SqliteTokenType::TK_SPACE;
+      return i;
+    }
+    case CC_MINUS: {
+      if (z[1] == '-') {
+        for (i = 2; (c = z[i]) != 0 && c != '\n'; i++) {
+        }
+        *tokenType = SqliteTokenType::TK_SPACE; /* IMP: R-22934-25134 */
+        return i;
+      } else if (z[1] == '>') {
+        *tokenType = SqliteTokenType::TK_PTR;
+        return 2 + (z[2] == '>');
+      }
+      *tokenType = SqliteTokenType::TK_MINUS;
+      return 1;
+    }
+    case CC_LP: {
+      *tokenType = SqliteTokenType::TK_LP;
+      return 1;
+    }
+    case CC_RP: {
+      *tokenType = SqliteTokenType::TK_RP;
+      return 1;
+    }
+    case CC_SEMI: {
+      *tokenType = SqliteTokenType::TK_SEMI;
+      return 1;
+    }
+    case CC_PLUS: {
+      *tokenType = SqliteTokenType::TK_PLUS;
+      return 1;
+    }
+    case CC_STAR: {
+      *tokenType = SqliteTokenType::TK_STAR;
+      return 1;
+    }
+    case CC_SLASH: {
+      if (z[1] != '*' || z[2] == 0) {
+        *tokenType = SqliteTokenType::TK_SLASH;
+        return 1;
+      }
+      for (i = 3, c = z[2]; (c != '*' || z[i] != '/') && (c = z[i]) != 0; i++) {
+      }
+      if (c)
+        i++;
+      *tokenType = SqliteTokenType::TK_SPACE; /* IMP: R-22934-25134 */
+      return i;
+    }
+    case CC_PERCENT: {
+      *tokenType = SqliteTokenType::TK_REM;
+      return 1;
+    }
+    case CC_EQ: {
+      *tokenType = SqliteTokenType::TK_EQ;
+      return 1 + (z[1] == '=');
+    }
+    case CC_LT: {
+      if ((c = z[1]) == '=') {
+        *tokenType = SqliteTokenType::TK_LE;
+        return 2;
+      } else if (c == '>') {
+        *tokenType = SqliteTokenType::TK_NE;
+        return 2;
+      } else if (c == '<') {
+        *tokenType = SqliteTokenType::TK_LSHIFT;
+        return 2;
+      } else {
+        *tokenType = SqliteTokenType::TK_LT;
+        return 1;
+      }
+    }
+    case CC_GT: {
+      if ((c = z[1]) == '=') {
+        *tokenType = SqliteTokenType::TK_GE;
+        return 2;
+      } else if (c == '>') {
+        *tokenType = SqliteTokenType::TK_RSHIFT;
+        return 2;
+      } else {
+        *tokenType = SqliteTokenType::TK_GT;
+        return 1;
+      }
+    }
+    case CC_BANG: {
+      if (z[1] != '=') {
+        *tokenType = SqliteTokenType::TK_ILLEGAL;
+        return 1;
+      } else {
+        *tokenType = SqliteTokenType::TK_NE;
+        return 2;
+      }
+    }
+    case CC_PIPE: {
+      if (z[1] != '|') {
+        *tokenType = SqliteTokenType::TK_BITOR;
+        return 1;
+      } else {
+        *tokenType = SqliteTokenType::TK_CONCAT;
+        return 2;
+      }
+    }
+    case CC_COMMA: {
+      *tokenType = SqliteTokenType::TK_COMMA;
+      return 1;
+    }
+    case CC_AND: {
+      *tokenType = SqliteTokenType::TK_BITAND;
+      return 1;
+    }
+    case CC_TILDA: {
+      *tokenType = SqliteTokenType::TK_BITNOT;
+      return 1;
+    }
+    case CC_QUOTE: {
+      int delim = z[0];
+      for (i = 1; (c = z[i]) != 0; i++) {
+        if (c == delim) {
+          if (z[i + 1] == delim) {
+            i++;
+          } else {
+            break;
+          }
+        }
+      }
+      if (c == '\'') {
+        *tokenType = SqliteTokenType::TK_STRING;
+        return i + 1;
+      } else if (c != 0) {
+        *tokenType = SqliteTokenType::TK_ID;
+        return i + 1;
+      } else {
+        *tokenType = SqliteTokenType::TK_ILLEGAL;
+        return i;
+      }
+    }
+    case CC_DOT: {
+      if (!isdigit(z[1])) {
+        *tokenType = SqliteTokenType::TK_DOT;
+        return 1;
+      }
+      [[fallthrough]];
+    }
+    case CC_DIGIT: {
+      *tokenType = SqliteTokenType::TK_INTEGER;
+      if (z[0] == '0' && (z[1] == 'x' || z[1] == 'X') && isxdigit(z[2])) {
+        for (i = 3; isxdigit(z[i]); i++) {
+        }
+        return i;
+      }
+      for (i = 0; isxdigit(z[i]); i++) {
+      }
+      if (z[i] == '.') {
+        i++;
+        while (isxdigit(z[i])) {
+          i++;
+        }
+        *tokenType = SqliteTokenType::TK_FLOAT;
+      }
+      if ((z[i] == 'e' || z[i] == 'E') &&
+          (isdigit(z[i + 1]) ||
+           ((z[i + 1] == '+' || z[i + 1] == '-') && isdigit(z[i + 2])))) {
+        i += 2;
+        while (isdigit(z[i])) {
+          i++;
+        }
+        *tokenType = SqliteTokenType::TK_FLOAT;
+      }
+      while (IdChar(z[i])) {
+        *tokenType = SqliteTokenType::TK_ILLEGAL;
+        i++;
+      }
+      return i;
+    }
+    case CC_QUOTE2: {
+      for (i = 1, c = z[0]; c != ']' && (c = z[i]) != 0; i++) {
+      }
+      *tokenType =
+          c == ']' ? SqliteTokenType::TK_ID : SqliteTokenType::TK_ILLEGAL;
+      return i;
+    }
+    case CC_VARNUM: {
+      *tokenType = SqliteTokenType::TK_VARIABLE;
+      for (i = 1; isdigit(z[i]); i++) {
+      }
+      return i;
+    }
+    case CC_DOLLAR:
+    case CC_VARALPHA: {
+      int n = 0;
+      *tokenType = SqliteTokenType::TK_VARIABLE;
+      for (i = 1; (c = z[i]) != 0; i++) {
+        if (IdChar(c)) {
+          n++;
+        } else if (c == '(' && n > 0) {
+          do {
+            i++;
+          } while ((c = z[i]) != 0 && !isspace(c) && c != ')');
+          if (c == ')') {
+            i++;
+          } else {
+            *tokenType = SqliteTokenType::TK_ILLEGAL;
+          }
+          break;
+        } else if (c == ':' && z[i + 1] == ':') {
+          i++;
+        } else {
+          break;
+        }
+      }
+      if (n == 0)
+        *tokenType = SqliteTokenType::TK_ILLEGAL;
+      return i;
+    }
+    case CC_KYWD0: {
+      for (i = 1; aiClass[z[i]] <= CC_KYWD; i++) {
+      }
+      if (IdChar(z[i])) {
+        /* This token started out using characters that can appear in keywords,
+        ** but z[i] is a character not allowed within keywords, so this must
+        ** be an identifier instead */
+        i++;
+        break;
+      }
+      if (sqlite3_keyword_check(reinterpret_cast<const char*>(z), i)) {
+        *tokenType = SqliteTokenType::TK_GENERIC_KEYWORD;
+      } else {
+        *tokenType = SqliteTokenType::TK_ID;
+      }
+      return i;
+    }
+    case CC_X: {
+      if (z[1] == '\'') {
+        *tokenType = SqliteTokenType::TK_BLOB;
+        for (i = 2; isdigit(z[i]); i++) {
+        }
+        if (z[i] != '\'' || i % 2) {
+          *tokenType = SqliteTokenType::TK_ILLEGAL;
+          while (z[i] && z[i] != '\'') {
+            i++;
+          }
+        }
+        if (z[i])
+          i++;
+        return i;
+      }
+      [[fallthrough]];
+    }
+    case CC_KYWD:
+    case CC_ID: {
+      i = 1;
+      break;
+    }
+    case CC_BOM: {
+      if (z[1] == 0xbb && z[2] == 0xbf) {
+        *tokenType = SqliteTokenType::TK_SPACE;
+        return 3;
+      }
+      i = 1;
+      break;
+    }
+    case CC_NUL: {
+      *tokenType = SqliteTokenType::TK_ILLEGAL;
+      return 0;
+    }
+    default: {
+      *tokenType = SqliteTokenType::TK_ILLEGAL;
+      return 1;
+    }
+  }
+  while (IdChar(z[i])) {
+    i++;
+  }
+  *tokenType = SqliteTokenType::TK_ID;
+  return i;
+}
+
+}  // namespace
+
+SqliteTokenizer::SqliteTokenizer(const char* sql) : ptr_(sql) {}
+
+SqliteTokenizer::Token SqliteTokenizer::Next() {
+  Token token;
+  const char* start = ptr_;
+  int n = GetSqliteToken(unsigned_ptr(), &token.token_type);
+  ptr_ += n;
+  token.str = std::string_view(start, static_cast<uint32_t>(n));
+  return token;
+}
+
+SqliteTokenizer::Token SqliteTokenizer::NextNonWhitespace() {
+  Token t;
+  for (t = Next(); t.token_type == SqliteTokenType::TK_SPACE; t = Next()) {
+  }
+  return t;
+}
+
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer.h b/src/trace_processor/sqlite/sqlite_tokenizer.h
new file mode 100644
index 0000000..f805d76
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_tokenizer.h
@@ -0,0 +1,111 @@
+/*
+ * 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_TOKENIZER_H_
+#define SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TOKENIZER_H_
+
+#include <optional>
+#include <string_view>
+
+namespace perfetto {
+namespace trace_processor {
+
+// List of token types returnable by |SqliteTokenizer|
+// 1:1 matches the defintions in SQLite.
+enum class SqliteTokenType : uint32_t {
+  TK_SEMI = 1,
+  TK_LP = 22,
+  TK_RP = 23,
+  TK_COMMA = 25,
+  TK_NE = 52,
+  TK_EQ = 53,
+  TK_GT = 54,
+  TK_LE = 55,
+  TK_LT = 56,
+  TK_GE = 57,
+  TK_ID = 59,
+  TK_BITAND = 102,
+  TK_BITOR = 103,
+  TK_LSHIFT = 104,
+  TK_RSHIFT = 105,
+  TK_PLUS = 106,
+  TK_MINUS = 107,
+  TK_STAR = 108,
+  TK_SLASH = 109,
+  TK_REM = 110,
+  TK_CONCAT = 111,
+  TK_PTR = 112,
+  TK_BITNOT = 114,
+  TK_STRING = 117,
+  TK_DOT = 141,
+  TK_FLOAT = 153,
+  TK_BLOB = 154,
+  TK_INTEGER = 155,
+  TK_VARIABLE = 156,
+  TK_SPACE = 183,
+  TK_ILLEGAL = 184,
+
+  // Generic constant which replaces all the keywords in SQLite as we do not
+  // care about the distinguishing between the vast majority of them.
+  TK_GENERIC_KEYWORD = 1000,
+};
+
+// Tokenizes SQL statements according to SQLite SQL language specification:
+// https://www2.sqlite.org/hlr40000.html
+//
+// Usage of this class:
+// SqliteTokenizer tzr(my_sql_string.c_str());
+// for (auto t = tzr.Next(); t.token_type != TK_SEMI; t = tzr.Next()) {
+//   // Handle t here
+// }
+class SqliteTokenizer {
+ public:
+  // A single SQL token according to the SQLite standard.
+  struct Token {
+    // The string contents of the token.
+    std::string_view str;
+
+    // The type of the token.
+    SqliteTokenType token_type;
+
+    bool operator==(const Token& o) const {
+      return str == o.str && token_type == o.token_type;
+    }
+  };
+
+  explicit SqliteTokenizer(const char* sql);
+
+  // Returns the next SQL token.
+  Token Next();
+
+  // Returns the next SQL token which is not of type TK_SPACE.
+  Token NextNonWhitespace();
+
+  // Returns the pointer to the start of the next token which will be returned.
+  const char* ptr() const { return ptr_; }
+
+ private:
+  const unsigned char* unsigned_ptr() const {
+    return reinterpret_cast<const unsigned char*>(ptr_);
+  }
+
+  const char* ptr_ = nullptr;
+};
+
+}  // namespace trace_processor
+}  // namespace perfetto
+
+#endif  // SRC_TRACE_PROCESSOR_SQLITE_SQLITE_TOKENIZER_H_
diff --git a/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc b/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc
new file mode 100644
index 0000000..44946b7
--- /dev/null
+++ b/src/trace_processor/sqlite/sqlite_tokenizer_unittest.cc
@@ -0,0 +1,67 @@
+/*
+ * 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_tokenizer.h"
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace trace_processor {
+namespace {
+
+using Token = SqliteTokenizer::Token;
+using Type = SqliteTokenType;
+
+class SqliteTokenizerTest : public ::testing::Test {
+ protected:
+  std::vector<SqliteTokenizer::Token> Tokenize(const char* ptr) {
+    SqliteTokenizer tokenizer(ptr);
+    std::vector<SqliteTokenizer::Token> tokens;
+    for (auto t = tokenizer.Next(); !t.str.empty(); t = tokenizer.Next()) {
+      tokens.push_back(t);
+    }
+    return tokens;
+  }
+};
+
+TEST_F(SqliteTokenizerTest, EmptyString) {
+  ASSERT_THAT(Tokenize(""), testing::IsEmpty());
+}
+
+TEST_F(SqliteTokenizerTest, OnlySpace) {
+  ASSERT_THAT(Tokenize(" "), testing::ElementsAre(Token{" ", Type::TK_SPACE}));
+}
+
+TEST_F(SqliteTokenizerTest, SpaceColon) {
+  ASSERT_THAT(Tokenize(" ;"), testing::ElementsAre(Token{" ", Type::TK_SPACE},
+                                                   Token{";", Type::TK_SEMI}));
+}
+
+TEST_F(SqliteTokenizerTest, Select) {
+  ASSERT_THAT(
+      Tokenize("SELECT * FROM slice;"),
+      testing::ElementsAre(
+          Token{"SELECT", Type::TK_GENERIC_KEYWORD}, Token{" ", Type::TK_SPACE},
+          Token{"*", Type::TK_STAR}, Token{" ", Type::TK_SPACE},
+          Token{"FROM", Type::TK_GENERIC_KEYWORD}, Token{" ", Type::TK_SPACE},
+          Token{"slice", Type::TK_ID}, Token{";", Type::TK_SEMI}));
+}
+
+}  // namespace
+}  // namespace trace_processor
+}  // namespace perfetto
diff --git a/src/trace_processor/sqlite/stats_table.cc b/src/trace_processor/sqlite/stats_table.cc
index c9b2ea8..296a2f5 100644
--- a/src/trace_processor/sqlite/stats_table.cc
+++ b/src/trace_processor/sqlite/stats_table.cc
@@ -25,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", RegistrationFlags{});
-}
+StatsTable::~StatsTable() = default;
 
 util::Status StatsTable::Init(int, const char* const*, Schema* schema) {
   *schema = Schema(
@@ -47,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*) {
@@ -56,7 +54,11 @@
 }
 
 StatsTable::Cursor::Cursor(StatsTable* table)
-    : SqliteTable::Cursor(table), table_(table), storage_(table->storage_) {}
+    : SqliteTable::BaseCursor(table),
+      table_(table),
+      storage_(table->storage_) {}
+
+StatsTable::Cursor::~Cursor() = default;
 
 base::Status StatsTable::Cursor::Filter(const QueryConstraints&,
                                         sqlite3_value**,
diff --git a/src/trace_processor/sqlite/stats_table.h b/src/trace_processor/sqlite/stats_table.h
index 648299f..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:
     explicit Cursor(StatsTable*);
+    ~Cursor() final;
 
     // Implementation of SqliteTable::Cursor.
     base::Status Filter(const QueryConstraints&,
                         sqlite3_value**,
-                        FilterHistory) override;
-    base::Status Next() override;
-    bool Eof() override;
-    base::Status Column(sqlite3_context*, int N) override;
+                        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..30cea54
--- /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 FLOAT   The counter value.
+-- @ret STRING        The human-readable name for the counter value.
+SELECT CREATE_FUNCTION(
+  'ANDROID_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 "disconnected"
+            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 "uninitialized"
+            ELSE "unknown"
+          END
+      WHEN $track = "battery_stats.data_conn"
+        THEN
+          CASE $value
+            WHEN 0 THEN "Out of service"
+            WHEN 1 THEN "2.5G (GPRS)"
+            WHEN 2 THEN "2.7G (EDGE)"
+            WHEN 3 THEN "3G (UMTS)"
+            WHEN 4 THEN "3G (CDMA)"
+            WHEN 5 THEN "3G (EVDO Rel 0)"
+            WHEN 6 THEN "3G (EVDO Rev A)"
+            WHEN 7 THEN "3G (LXRTT)"
+            WHEN 8 THEN "3.5G (HSDPA)"
+            WHEN 9 THEN "3.5G (HSUPA)"
+            WHEN 10 THEN "3.5G (HSPA)"
+            WHEN 11 THEN "2G (IDEN)"
+            WHEN 12 THEN "3G (EVDO Rev B)"
+            WHEN 13 THEN "4G (LTE)"
+            WHEN 14 THEN "3.5G (eHRPD)"
+            WHEN 15 THEN "3.7G (HSPA+)"
+            WHEN 16 THEN "2G (GSM)"
+            WHEN 17 THEN "3G (TD SCDMA)"
+            WHEN 18 THEN "Wifi calling (IWLAN)"
+            WHEN 19 THEN "4.5G (LTE CA)"
+            WHEN 20 THEN "5G (NR)"
+            WHEN 21 THEN "Emergency calls only"
+            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,
+  ANDROID_BATTERY_STATS_COUNTER_TO_STRING(name, value) AS value_name,
+  IFNULL(LEAD(ts) OVER (PARTITION BY name ORDER BY ts) - ts, -1) AS dur
+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/monitor_contention.sql b/src/trace_processor/stdlib/android/monitor_contention.sql
index c1de58f..9fae0c7 100644
--- a/src/trace_processor/stdlib/android/monitor_contention.sql
+++ b/src/trace_processor/stdlib/android/monitor_contention.sql
@@ -145,7 +145,7 @@
 AS
 SELECT ancestor.parent_id AS id FROM slice
     JOIN slice ancestor ON ancestor.id = slice.parent_id
-    WHERE ancestor.name LIKE 'Lock contention on a monitor lock%'
+    WHERE ancestor.name GLOB 'Lock contention on a monitor lock*'
     GROUP BY ancestor.id;
 
 -- Contains parsed monitor contention slices.
@@ -192,10 +192,13 @@
   slice.dur,
   slice.track_id,
   thread.is_main_thread AS is_blocked_thread_main,
+  thread.tid AS blocked_thread_tid,
   blocking_thread.is_main_thread AS is_blocking_thread_main,
+  blocking_thread.tid AS blocking_thread_tid,
   binder_reply.id AS binder_reply_id,
   binder_reply.ts AS binder_reply_ts,
-  binder_reply_thread.tid AS binder_reply_tid
+  binder_reply_thread.tid AS binder_reply_tid,
+  process.pid
 FROM slice
 JOIN thread_track
   ON thread_track.id = slice.track_id
@@ -208,7 +211,7 @@
 LEFT JOIN thread_track binder_reply_thread_track ON binder_reply.track_id = binder_reply_thread_track.id
 LEFT JOIN thread binder_reply_thread ON binder_reply_thread_track.utid = binder_reply_thread.utid
 JOIN thread blocking_thread ON blocking_thread.tid = blocking_tid AND blocking_thread.upid = thread.upid
-WHERE slice.name LIKE 'monitor contention%'
+WHERE slice.name GLOB 'monitor contention*'
   AND slice.dur != -1
   AND internal_broken_android_monitor_contention.id IS NULL
   AND short_blocking_method IS NOT NULL
@@ -244,13 +247,34 @@
 LEFT JOIN android_monitor_contention parent ON child.blocked_utid = parent.blocking_utid
     AND parent.ts BETWEEN child.ts AND child.ts + child.dur;
 
+-- First blocked node on a lock, i.e nodes with |waiter_count| = 0. The |dur| here is adjusted
+-- to only account for the time between the first thread waiting and the first thread to acquire
+-- the lock. That way, the thread state span joins below only compute the thread states where
+-- the blocking thread is actually holding the lock. This avoids counting the time when another
+-- waiter acquired the lock before the first waiter.
+CREATE VIEW internal_first_blocked_contention
+  AS
+SELECT start.id, start.blocking_utid, start.ts, MIN(end.ts + end.dur) - start.ts AS dur
+FROM android_monitor_contention_chain start
+JOIN android_monitor_contention_chain END
+  ON
+    start.blocking_utid = end.blocking_utid
+    AND start.blocking_method = end.blocking_method
+    AND end.ts BETWEEN start.ts AND start.ts + start.dur
+WHERE start.waiter_count = 0
+GROUP BY start.id;
+
 CREATE VIEW internal_blocking_thread_state
 AS
 SELECT utid AS blocking_utid, ts, dur, state, blocked_function
 FROM thread_state;
 
--- Contains the span join of the |android_monitor_contention_chain| with their
--- blocking thread thread state.
+-- Contains the span join of the first waiters in the |android_monitor_contention_chain| with their
+-- blocking_thread thread state.
+
+-- Note that we only span join the duration where the lock was actually held and contended.
+-- This can be less than the duration the lock was 'waited on' when a different waiter acquired the
+-- lock earlier than the first waiter.
 --
 -- @column parent_id Id of slice blocking the blocking_thread.
 -- @column blocking_method Name of the method holding the lock.
@@ -279,14 +303,16 @@
 -- @column blocked_function Blocked kernel function of the blocking thread.
 CREATE VIRTUAL TABLE android_monitor_contention_chain_thread_state
 USING
-  SPAN_JOIN(android_monitor_contention_chain PARTITIONED blocking_utid,
+  SPAN_JOIN(internal_first_blocked_contention PARTITIONED blocking_utid,
             internal_blocking_thread_state PARTITIONED blocking_utid);
 
--- Aggregated blocked_functions on the 'blocking thread', the thread holding the lock.
+-- Aggregated thread_states on the 'blocking thread', the thread holding the lock.
 -- This builds on the data from |android_monitor_contention_chain| and
 -- for each contention slice, it returns the aggregated sum of all the thread states on the
 -- blocking thread.
 --
+-- Note that this data is only available for the first waiter on a lock.
+--
 -- @column id Slice id of the monitor contention.
 -- @column thread_state A |thread_state| that occurred in the blocking thread during the contention.
 -- @column thread_state_dur Total time the blocking thread spent in the |thread_state| during
@@ -308,6 +334,8 @@
 -- for each contention, it returns the aggregated sum of all the kernel
 -- blocked function durations on the blocking thread.
 --
+-- Note that this data is only available for the first waiter on a lock.
+--
 -- @column id Slice id of the monitor contention.
 -- @column blocked_function Blocked kernel function in a thread state in the blocking thread during
 -- the contention.
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..e9881c6 100644
--- a/src/trace_processor/stdlib/chrome/BUILD.gn
+++ b/src/trace_processor/stdlib/chrome/BUILD.gn
@@ -15,5 +15,10 @@
 import("../../../../gn/perfetto_sql.gni")
 
 perfetto_sql_source_set("chrome_sql") {
-  sources = [ "cpu_powerups.sql" ]
+  sources = [
+    "chrome_scrolls.sql",
+    "cpu_powerups.sql",
+    "histograms.sql",
+    "speedometer.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/chrome/histograms.sql b/src/trace_processor/stdlib/chrome/histograms.sql
new file mode 100644
index 0000000..db354e3
--- /dev/null
+++ b/src/trace_processor/stdlib/chrome/histograms.sql
@@ -0,0 +1,46 @@
+-- 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_histograms;
+
+-- A helper view on top of the histogram events emitted by Chrome.
+-- Requires "disabled-by-default-histogram_samples" Chrome category.
+--
+-- @column name          The name of the histogram.
+-- @column value         The value of the histogram sample.
+-- @column ts            Alias of |slice.ts|.
+-- @column thread_name   Thread name.
+-- @column utid          Utid of the thread.
+-- @column tid           Tid of the thread.
+-- @column process_name  Process name.
+-- @column upid          Upid of the process.
+-- @column pid           Pid of the process.
+CREATE VIEW chrome_histograms AS
+SELECT
+  extract_arg(slice.arg_set_id, "chrome_histogram_sample.name") as name,
+  extract_arg(slice.arg_set_id, "chrome_histogram_sample.sample") as value,
+  ts,
+  thread.name as thread_name,
+  thread.utid as utid,
+  thread.tid as tid,
+  process.name as process_name,
+  process.upid as upid,
+  process.pid as pid
+FROM slice
+JOIN thread_track ON thread_track.id = slice.track_id
+JOIN thread USING (utid)
+JOIN process USING (upid)
+WHERE
+  slice.name = "HistogramSample"
+  AND category = "disabled-by-default-histogram_samples";
\ No newline at end of file
diff --git a/src/trace_processor/stdlib/chrome/speedometer.sql b/src/trace_processor/stdlib/chrome/speedometer.sql
new file mode 100644
index 0000000..652940a
--- /dev/null
+++ b/src/trace_processor/stdlib/chrome/speedometer.sql
@@ -0,0 +1,202 @@
+-- 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.
+
+-- Annotates a trace with Speedometer 2.1 related information.
+--
+-- The scripts below analyse traces with the following tracing options
+-- enabled:
+--
+--  - Chromium:
+--      "blink.user_timing".
+--
+--  NOTE: A regular speedometer run (e.g. from the website) will generate the
+--  required events. No need to add any extra JS or anything.
+--
+-- Noteworthy tables:
+--   speedometer_mark: List of marks (event slices) emitted by Speedometer.
+--       These are the points in time Speedometer makes a clock reading to
+--       compute intervals of time for the final score.
+--   speedometer_measure_slice: Augmented slices for Speedometer measurements.
+--       These are the intervals of time Speedometer uses to compute the final
+--       score.
+--   speedometer_iteration_slice: Slice that covers one Speedometer iteration
+--       and has the total_time and score for it. If you average all the scores
+--       over all iterations you get the final Speedometer score for the run.
+
+-- List of marks (event slices) emitted by Speedometer.
+-- These are the points in time Speedometer makes a clock reading to compute
+-- intervals of time for the final score.
+--
+-- @column slice_id      Slice this data refers to.
+-- @column iteration     Speedometer iteration the mark belongs to.
+-- @column suite_name    Suite name
+-- @column test_name     Test name
+-- @column mark_type     Type of mark (start, sync-end, async-end)
+CREATE VIEW internal_chrome_speedometer_mark
+AS
+WITH
+  speedometer_21_suite_name(suite_name) AS (
+    VALUES
+      ('VanillaJS-TodoMVC'),
+      ('Vanilla-ES2015-TodoMVC'),
+      ('Vanilla-ES2015-Babel-Webpack-TodoMVC'),
+      ('React-TodoMVC'),
+      ('React-Redux-TodoMVC'),
+      ('EmberJS-TodoMVC'),
+      ('EmberJS-Debug-TodoMVC'),
+      ('BackboneJS-TodoMVC'),
+      ('AngularJS-TodoMVC'),
+      ('Angular2-TypeScript-TodoMVC'),
+      ('VueJS-TodoMVC'),
+      ('jQuery-TodoMVC'),
+      ('Preact-TodoMVC'),
+      ('Inferno-TodoMVC'),
+      ('Elm-TodoMVC'),
+      ('Flight-TodoMVC')
+  ),
+  speedometer_21_test_name(test_name) AS (
+    VALUES
+      ('Adding100Items'),
+      ('CompletingAllItems'),
+      -- This seems to be an issue with Speedometer 2.1. All tests delete all items,
+      -- but for some reason the test names do not match for all suites.
+      ('DeletingAllItems'),
+      ('DeletingItems')
+  ),
+  speedometer_21_test_mark_type(mark_type) AS (
+    VALUES
+      ('start'),
+      ('sync-end'),
+      ('async-end')
+  ),
+  -- Make sure we only look at slices with names we expect.
+  speedometer_mark_name AS (
+    SELECT
+      s.suite_name || '.' || t.test_name || '-' || m.mark_type AS name,
+      s.suite_name,
+      t.test_name,
+      m.mark_type
+    FROM
+      speedometer_21_suite_name AS s,
+      speedometer_21_test_name AS t,
+      speedometer_21_test_mark_type AS m
+  )
+SELECT
+  s.id AS slice_id,
+  RANK() OVER (PARTITION BY name ORDER BY ts ASC) AS iteration,
+  m.suite_name,
+  m.test_name,
+  m.mark_type
+FROM slice AS s
+JOIN speedometer_mark_name AS m
+  USING (name)
+WHERE category = 'blink.user_timing';
+
+-- Augmented slices for Speedometer measurements.
+-- These are the intervals of time Speedometer uses to compute the final score.
+-- There are two intervals that are measured for every test: sync and async
+-- sync is the time between the start and sync-end marks, async is the time
+-- between the sync-end and async-end marks.
+--
+-- @column iteration     Speedometer iteration the mark belongs to.
+-- @column suite_name    Suite name
+-- @column test_name     Test name
+-- @column measure_type  Type of the measure (sync or async)
+-- @column ts            Start timestamp of the measure
+-- @column dur           Duration of the measure
+CREATE VIEW chrome_speedometer_measure
+AS
+WITH
+  -- Get the 3 test timestamps (start, sync-end, async-end) in one row. Using a
+  -- the LAG window function and partitioning by test. 2 out of the 3 rows
+  -- generated per test will have some NULL ts values.
+  augmented AS (
+    SELECT
+      iteration,
+      suite_name,
+      test_name,
+      ts AS async_end_ts,
+      LAG(ts, 1)
+        OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC)
+        AS sync_end_ts,
+      LAG(ts, 2)
+        OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC)
+        AS start_ts,
+      COUNT()
+        OVER (PARTITION BY iteration, suite_name, test_name ORDER BY ts ASC)
+        AS mark_count
+    FROM internal_chrome_speedometer_mark
+    JOIN slice
+      USING (slice_id)
+  ),
+  filtered AS (
+    SELECT *
+    FROM augmented
+    -- This server 2 purposes: make sure we have all the marks (think truncated
+    -- trace), and remove the NULL ts values due to the LAG window function.
+    WHERE mark_count = 3
+  )
+SELECT
+  iteration,
+  suite_name,
+  test_name,
+  'async' AS measure_type,
+  sync_end_ts AS ts,
+  async_end_ts - sync_end_ts AS dur
+FROM filtered
+UNION ALL
+SELECT
+  iteration,
+  suite_name,
+  test_name,
+  'sync' AS measure_type,
+  start_ts AS ts,
+  sync_end_ts - start_ts AS dur
+FROM filtered;
+
+-- Slice that covers one Speedometer iteration.
+-- This slice is actually estimated as a default Speedometer run will not emit
+-- marks to cover this interval. The metrics associated are the same ones
+-- Speedometer would output, but note we use ns precision (Speedometer uses
+-- ~100us) so the actual values might differ a bit. Also note Speedometer
+-- returns the values in ms these here and in ns.
+--
+-- @column iteration Speedometer iteration.
+-- @column ts        Start timestamp of the iteration
+-- @column dur       Duration of the iteration
+-- @column total     Total duration of the measures in this iteration
+-- @column mean      Average suite duration for this iteration.
+-- @column geomean   Geometric mean of the suite durations for this iteration.
+-- @column score     Speedometer score for this iteration (The total score for a
+--                   run in the average of all iteration scores).
+CREATE VIEW chrome_speedometer_iteration
+AS
+SELECT
+  iteration,
+  MIN(start) AS ts,
+  MAX(end) - MIN(start) AS dur,
+  SUM(suite_total) AS total,
+  AVG(suite_total)AS mean,
+  -- Compute geometric mean using LN instead of multiplication to prevent
+  -- overflows
+  EXP(AVG(LN(suite_total))) AS geomean,
+  1e9 / EXP(AVG(LN(suite_total))) * 60 / 3 AS score
+FROM
+  (
+    SELECT
+      iteration, SUM(dur) AS suite_total, MIN(ts) AS start, MAX(ts + dur) AS end
+    FROM chrome_speedometer_measure
+    GROUP BY suite_name, iteration
+  )
+GROUP BY iteration;
diff --git a/src/trace_processor/stdlib/common/cpus.sql b/src/trace_processor/stdlib/common/cpus.sql
index 3caec3f..460c80c 100644
--- a/src/trace_processor/stdlib/common/cpus.sql
+++ b/src/trace_processor/stdlib/common/cpus.sql
@@ -17,9 +17,9 @@
 CREATE TABLE internal_cpu_sizes AS
 SELECT 0 AS n, 'little' AS size
 UNION
-SELECT 1 AS n, 'big' AS size
+SELECT 1 AS n, 'mid' AS size
 UNION
-SELECT 2 AS n, 'huge' AS size;
+SELECT 2 AS n, 'big' AS size;
 
 CREATE TABLE internal_ranked_cpus AS
 SELECT
@@ -46,7 +46,7 @@
 -- 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.
+-- @ret STRING          A descriptive size ('little', 'mid', 'big', etc) or NULL if we have insufficient information.
 SELECT CREATE_FUNCTION(
   'GUESS_CPU_SIZE(cpu_index INT)',
   'STRING',
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 66fc940..67bdd0b 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -42,6 +42,7 @@
 #include "src/trace_processor/tables/memory_tables_py.h"
 #include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/tables/sched_tables_py.h"
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/trace_proto_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
@@ -457,6 +458,13 @@
   const tables::SliceTable& slice_table() const { return slice_table_; }
   tables::SliceTable* mutable_slice_table() { return &slice_table_; }
 
+  const tables::SpuriousSchedWakeupTable& spurious_sched_wakeup_table() const {
+    return spurious_sched_wakeup_table_;
+  }
+  tables::SpuriousSchedWakeupTable* mutable_spurious_sched_wakeup_table() {
+    return &spurious_sched_wakeup_table_;
+  }
+
   const tables::FlowTable& flow_table() const { return flow_table_; }
   tables::FlowTable* mutable_flow_table() { return &flow_table_; }
 
@@ -888,6 +896,8 @@
   // Slices from CPU scheduling data.
   tables::SchedSliceTable sched_slice_table_{&string_pool_};
 
+  tables::SpuriousSchedWakeupTable spurious_sched_wakeup_table_{&string_pool_};
+
   // Additional attributes for virtual track slices (sub-type of
   // NestableSlices).
   VirtualTrackSlices virtual_track_slices_;
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 9ec397f..b8d69a8 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -23,6 +23,7 @@
     "memory_tables.py",
     "metadata_tables.py",
     "profiler_tables.py",
+    "sched_tables.py",
     "slice_tables.py",
     "trace_proto_tables.py",
     "track_tables.py",
diff --git a/src/trace_processor/tables/metadata_tables.py b/src/trace_processor/tables/metadata_tables.py
index 0219aa9..093db01 100644
--- a/src/trace_processor/tables/metadata_tables.py
+++ b/src/trace_processor/tables/metadata_tables.py
@@ -170,6 +170,7 @@
         C('cpu', CppUint32()),
         C('utid', CppTableId(THREAD_TABLE)),
         C('arg_set_id', CppUint32()),
+        C('common_flags', CppUint32())
     ],
     tabledoc=TableDoc(
         doc='''
@@ -177,7 +178,7 @@
           table only exists for debugging purposes and should not be relied on
           in production usecases (i.e. metrics, standard library etc).
         ''',
-        group='Misc',
+        group='Events',
         columns={
             'arg_set_id':
                 ColumnDoc(
@@ -193,7 +194,9 @@
             'cpu':
                 'The CPU this event was emitted on.',
             'utid':
-                'The thread this event was emitted on.'
+                'The thread this event was emitted on.',
+            'common_flags':
+                'Ftrace event flags for this event. Currently only emitted for sched_waking events.'
         }))
 
 FTRACE_EVENT_TABLE = Table(
@@ -204,12 +207,12 @@
     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',
+          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='Events',
         columns={}))
 
 ARG_TABLE = Table(
diff --git a/src/trace_processor/tables/sched_tables.py b/src/trace_processor/tables/sched_tables.py
new file mode 100644
index 0000000..d65c8be
--- /dev/null
+++ b/src/trace_processor/tables/sched_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 relevant for sched."""
+
+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
+from python.generators.trace_processor_table.public import CppOptional
+from python.generators.trace_processor_table.public import CppSelfTableId
+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 python.generators.trace_processor_table.public import TableDoc
+from python.generators.trace_processor_table.public import WrappingSqlView
+
+SCHED_SLICE_TABLE = Table(
+    python_module=__file__,
+    class_name='SchedSliceTable',
+    sql_name='sched_slice',
+    columns=[
+        C('ts', CppInt64(), flags=ColumnFlag.SORTED),
+        C('dur', CppInt64()),
+        C('cpu', CppUint32()),
+        C('utid', CppUint32()),
+        C('end_state', CppString()),
+        C('priority', CppInt32()),
+    ],
+    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.
+
+          The rows in this table will always have a matching row in the
+          |thread_state| table with |thread_state.state| = 'Running'
+        ''',
+        group='Events',
+        columns={
+            'ts':
+                '''The timestamp at the start of the slice (in nanoseconds).''',
+            'dur':
+                '''The duration of the slice (in nanoseconds).''',
+            'utid':
+                '''The thread's unique id in the trace..''',
+            '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).
+                ''',
+            'priority':
+                '''The kernel priority that the thread ran at.'''
+        }))
+
+SPURIOUS_SCHED_WAKEUP_TABLE = Table(
+    python_module=__file__,
+    class_name='SpuriousSchedWakeupTable',
+    sql_name='spurious_sched_wakeup',
+    columns=[
+        C('ts', CppInt64(), flags=ColumnFlag.SORTED),
+        C('thread_state_id', CppInt64()),
+        C('irq_context', CppOptional(CppUint32())),
+        C('utid', CppUint32()),
+        C('waker_utid', CppUint32()),
+    ],
+    tabledoc=TableDoc(
+        doc='''
+          This table contains the scheduling wakeups that occurred while a thread was
+          not blocked, i.e. running or runnable. Such wakeups are not tracked in the
+          |thread_state_table|.
+        ''',
+        group='Events',
+        columns={
+            'ts':
+                'The timestamp at the start of the slice (in nanoseconds).',
+            'thread_state_id':
+                'The id of the row in the thread_state table that this row is associated with.',
+            'irq_context':
+                '''Whether the wakeup was from interrupt context or process context.''',
+            'utid':
+                '''The thread's unique id in the trace..''',
+            'waker_utid':
+                '''
+                  The unique thread id of the thread which caused a wakeup of
+                  this thread.
+                '''
+        }))
+
+THREAD_STATE_TABLE = Table(
+    python_module=__file__,
+    class_name='ThreadStateTable',
+    sql_name='thread_state',
+    columns=[
+        C('ts', CppInt64(), flags=ColumnFlag.SORTED),
+        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())),
+        C('irq_context', CppOptional(CppUint32())),
+    ],
+    tabledoc=TableDoc(
+        doc='''
+          This table contains the scheduling state of every thread on the
+          system during the trace.
+
+          The rows in this table which have |state| = 'Running', will have a
+          corresponding row in the |sched_slice| table.
+        ''',
+        group='Events',
+        columns={
+            '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.''',
+            'irq_context':
+                '''Whether the wakeup was from interrupt context or process context.''',
+            '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.
+                '''
+        }))
+
+# Keep this list sorted.
+ALL_TABLES = [
+    SCHED_SLICE_TABLE,
+    SPURIOUS_SCHED_WAKEUP_TABLE,
+    THREAD_STATE_TABLE,
+]
diff --git a/src/trace_processor/tables/slice_tables.py b/src/trace_processor/tables/slice_tables.py
index 22bd9b3..f1a4d90 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
@@ -50,91 +51,73 @@
     ],
     wrapping_sql_view=WrappingSqlView('slice'),
     tabledoc=TableDoc(
-        doc='''''',
-        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': ''''''
-        }))
-
-SCHED_SLICE_TABLE = Table(
-    python_module=__file__,
-    class_name='SchedSliceTable',
-    sql_name='sched_slice',
-    columns=[
-        C('ts', CppInt64(), flags=ColumnFlag.SORTED),
-        C('dur', CppInt64()),
-        C('cpu', CppUint32()),
-        C('utid', CppUint32()),
-        C('end_state', CppString()),
-        C('priority', CppInt32()),
-    ],
-    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.
+          Contains slices from userspace which explains what threads were doing
+          during the trace.
         ''',
         group='Events',
         columns={
             'ts':
-                '''The timestamp at the start of the slice (in nanoseconds).''',
+                'The timestamp at the start of the slice (in nanoseconds).',
             'dur':
-                '''The duration of the slice (in nanoseconds).''',
-            'utid':
-                '''The thread's unique id in the trace..''',
-            '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).''',
-            '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('state', CppString()),
-        C('io_wait', CppOptional(CppUint32())),
-        C('blocked_function', CppOptional(CppString())),
-        C('waker_utid', CppOptional(CppUint32())),
-    ],
-    tabledoc=TableDoc(
-        doc='''''',
-        group='Events',
-        columns={
-            'utid': '''''',
-            'ts': '''''',
-            'dur': '''''',
-            'cpu': '''''',
-            'state': '''''',
-            'io_wait': '''''',
-            'blocked_function': '''''',
-            'waker_utid': ''''''
+                '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.
+                ''',
         }))
 
 GPU_SLICE_TABLE = Table(
@@ -208,7 +191,7 @@
     parent=SLICE_TABLE,
     tabledoc=TableDoc(
         doc='''''',
-        group='Misc',
+        group='Events',
         columns={
             'display_frame_token': '''''',
             'surface_frame_token': '''''',
@@ -235,7 +218,7 @@
     parent=SLICE_TABLE,
     tabledoc=TableDoc(
         doc='''''',
-        group='Misc',
+        group='Events',
         columns={
             'display_frame_token': '''''',
             'surface_frame_token': '''''',
@@ -265,18 +248,35 @@
         C('end_bound', CppInt64(), flags=ColumnFlag.HIDDEN),
     ],
     tabledoc=TableDoc(
-        doc='''''',
-        group='Misc',
+        doc='''
+          An experimental table which "flattens" stacks of slices to contain
+          only the "deepest" slice at any point in time on each track.
+        ''',
+        group='Events',
         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.
@@ -286,7 +286,5 @@
     EXPERIMENTAL_FLAT_SLICE_TABLE,
     GPU_SLICE_TABLE,
     GRAPHICS_FRAME_SLICE_TABLE,
-    SCHED_SLICE_TABLE,
     SLICE_TABLE,
-    THREAD_STATE_TABLE,
 ]
diff --git a/src/trace_processor/tables/table_destructors.cc b/src/trace_processor/tables/table_destructors.cc
index 3dcf113..ba57b22 100644
--- a/src/trace_processor/tables/table_destructors.cc
+++ b/src/trace_processor/tables/table_destructors.cc
@@ -20,6 +20,7 @@
 #include "src/trace_processor/tables/memory_tables_py.h"
 #include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/tables/sched_tables_py.h"
 #include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/tables/trace_proto_tables_py.h"
 #include "src/trace_processor/tables/track_tables_py.h"
@@ -71,13 +72,16 @@
 ProfilerSmapsTable::~ProfilerSmapsTable() = default;
 GpuCounterGroupTable::~GpuCounterGroupTable() = default;
 
+// sched_tables_py.h
+SchedSliceTable::~SchedSliceTable() = default;
+SpuriousSchedWakeupTable::~SpuriousSchedWakeupTable() = default;
+ThreadStateTable::~ThreadStateTable() = default;
+
 // slice_tables_py.h
 SliceTable::~SliceTable() = default;
 FlowTable::~FlowTable() = default;
-SchedSliceTable::~SchedSliceTable() = default;
 GpuSliceTable::~GpuSliceTable() = default;
 GraphicsFrameSliceTable::~GraphicsFrameSliceTable() = default;
-ThreadStateTable::~ThreadStateTable() = default;
 ExpectedFrameTimelineSliceTable::~ExpectedFrameTimelineSliceTable() = default;
 ActualFrameTimelineSliceTable::~ActualFrameTimelineSliceTable() = default;
 ExperimentalFlatSliceTable::~ExperimentalFlatSliceTable() = default;
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/tp_metatrace.h b/src/trace_processor/tp_metatrace.h
index b800483..e4f4627 100644
--- a/src/trace_processor/tp_metatrace.h
+++ b/src/trace_processor/tp_metatrace.h
@@ -172,7 +172,7 @@
     record_->duration_ns = now - record_->timestamp_ns;
   }
 
-  ScopedEvent(ScopedEvent&& value) {
+  ScopedEvent(ScopedEvent&& value) noexcept {
     record_ = value.record_;
     record_idx_ = value.record_idx_;
     value.record_ = nullptr;
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 5e10fc1..800d6d6 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -52,8 +52,9 @@
 #include "src/trace_processor/prelude/functions/create_view_function.h"
 #include "src/trace_processor/prelude/functions/import.h"
 #include "src/trace_processor/prelude/functions/layout_functions.h"
+#include "src/trace_processor/prelude/functions/math.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"
@@ -73,6 +74,7 @@
 #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/perfetto_sql_engine.h"
 #include "src/trace_processor/sqlite/scoped_db.h"
 #include "src/trace_processor/sqlite/sql_stats_table.h"
 #include "src/trace_processor/sqlite/sqlite_table.h"
@@ -106,13 +108,13 @@
     "* FROM sqlite_temp_master)";
 
 template <typename SqlFunction, typename Ptr = typename SqlFunction::Context*>
-void RegisterFunction(sqlite3* db,
+void RegisterFunction(PerfettoSqlEngine* 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());
 }
@@ -238,7 +240,7 @@
 }
 
 void SetupMetrics(TraceProcessor* tp,
-                  sqlite3* db,
+                  PerfettoSqlEngine* engine,
                   std::vector<metrics::SqlMetricFile>* sql_metrics,
                   const std::vector<std::string>& extension_paths) {
   const std::vector<std::string> sanitized_extension_paths =
@@ -268,10 +270,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}));
 
@@ -279,8 +282,9 @@
   // functions are supported.
   {
     auto ret = sqlite3_create_function_v2(
-        db, "RepeatedField", 1, SQLITE_UTF8, nullptr, nullptr,
-        metrics::RepeatedFieldStep, metrics::RepeatedFieldFinal, nullptr);
+        engine->sqlite_engine()->db(), "RepeatedField", 1, SQLITE_UTF8, nullptr,
+        nullptr, metrics::RepeatedFieldStep, metrics::RepeatedFieldFinal,
+        nullptr);
     if (ret)
       PERFETTO_FATAL("Error initializing RepeatedField");
   }
@@ -298,137 +302,6 @@
   }
 }
 
-void IncrementCountForStmt(sqlite3_stmt* stmt,
-                           IteratorImpl::StmtMetadata* metadata) {
-  metadata->statement_count++;
-
-  // If the stmt is already done, it clearly didn't have any output.
-  if (sqlite_utils::IsStmtDone(stmt))
-    return;
-
-  if (sqlite3_column_count(stmt) == 1) {
-    sqlite3_value* value = sqlite3_column_value(stmt, 0);
-
-    // If the "VOID" pointer associated to the return value is not null,
-    // that means this is a function which is forced to return a value
-    // (because all functions in SQLite have to) but doesn't actually
-    // wait to (i.e. it wants to be treated like CREATE TABLE or similar).
-    // Because of this, ignore the return value of this function.
-    // See |WrapSqlFunction| for where this is set.
-    if (sqlite3_value_pointer(value, "VOID") != nullptr) {
-      return;
-    }
-
-    // If the statement only has a single column and that column is named
-    // "suppress_query_output", treat it as a statement without output for
-    // accounting purposes. This allows an escape hatch for cases where the
-    // user explicitly wants to ignore functions as having output.
-    if (strcmp(sqlite3_column_name(stmt, 0), "suppress_query_output") == 0) {
-      return;
-    }
-  }
-
-  // Otherwise, the statement has output and so increment the count.
-  metadata->statement_count_with_output++;
-}
-
-base::Status PrepareAndStepUntilLastValidStmt(
-    sqlite3* db,
-    const std::string& sql,
-    ScopedStmt* output_stmt,
-    IteratorImpl::StmtMetadata* metadata) {
-  ScopedStmt prev_stmt;
-  // A sql string can contain several statements. Some of them might be comment
-  // only, e.g. "SELECT 1; /* comment */; SELECT 2;". Here we process one
-  // statement on each iteration. SQLite's sqlite_prepare_v2 (wrapped by
-  // PrepareStmt) returns on each iteration a pointer to the unprocessed string.
-  //
-  // Unfortunately we cannot call PrepareStmt and tokenize all statements
-  // upfront because sqlite_prepare_v2 also semantically checks the statement
-  // against the schema. In some cases statements might depend on the execution
-  // of previous ones (e.e. CREATE VIEW x; SELECT FROM x; DELETE VIEW x;).
-  //
-  // Also, unfortunately, we need to PrepareStmt to find out if a statement is a
-  // comment or a real statement.
-  //
-  // The logic here is the following:
-  //  - We invoke PrepareStmt on each statement.
-  //  - If the statement is a comment we simply skip it.
-  //  - If the statement is valid, we step once to make sure side effects take
-  //    effect.
-  //  - If we encounter a valid statement afterwards, we step internally through
-  //    all rows of the previous one. This ensures that any further side effects
-  //    take hold *before* we step into the next statement.
-  //  - Once no further non-comment statements are encountered, we return an
-  //    iterator to the last valid statement.
-  for (const char* rem_sql = sql.c_str(); rem_sql && rem_sql[0];) {
-    ScopedStmt cur_stmt;
-    {
-      PERFETTO_TP_TRACE(metatrace::Category::QUERY, "QUERY_PREPARE");
-      const char* tail = nullptr;
-      RETURN_IF_ERROR(sqlite_utils::PrepareStmt(db, rem_sql, &cur_stmt, &tail));
-      rem_sql = tail;
-    }
-
-    // The only situation where we'd have an ok status but also no prepared
-    // statement is if the statement of SQL we parsed was a pure comment. In
-    // this case, just continue to the next statement.
-    if (!cur_stmt)
-      continue;
-
-    // Before stepping into |cur_stmt|, we need to finish iterating through
-    // the previous statement so we don't have two clashing statements (e.g.
-    // SELECT * FROM v and DROP VIEW v) partially stepped into.
-    if (prev_stmt) {
-      PERFETTO_TP_TRACE(metatrace::Category::QUERY, "STMT_STEP_UNTIL_DONE",
-                        [&prev_stmt](metatrace::Record* record) {
-                          auto expanded_sql =
-                              sqlite_utils::ExpandedSqlForStmt(*prev_stmt);
-                          record->AddArg("SQL", expanded_sql.get());
-                        });
-      RETURN_IF_ERROR(sqlite_utils::StepStmtUntilDone(prev_stmt.get()));
-    }
-
-    PERFETTO_DLOG("Executing statement: %s", sqlite3_sql(*cur_stmt));
-
-    {
-      PERFETTO_TP_TRACE(metatrace::Category::TOPLEVEL, "STMT_FIRST_STEP",
-                        [&cur_stmt](metatrace::Record* record) {
-                          auto expanded_sql =
-                              sqlite_utils::ExpandedSqlForStmt(*cur_stmt);
-                          record->AddArg("SQL", expanded_sql.get());
-                        });
-
-      // Now step once into |cur_stmt| so that when we prepare the next statment
-      // we will have executed any dependent bytecode in this one.
-      int err = sqlite3_step(*cur_stmt);
-      if (err != SQLITE_ROW && err != SQLITE_DONE) {
-        return base::ErrStatus(
-            "%s", sqlite_utils::FormatErrorMessage(
-                      prev_stmt.get(), base::StringView(sql), db, err)
-                      .c_message());
-      }
-    }
-
-    // Increment the neecessary counts for the statement.
-    IncrementCountForStmt(cur_stmt.get(), metadata);
-
-    // Propogate the current statement to the next iteration.
-    prev_stmt = std::move(cur_stmt);
-  }
-
-  // If we didn't manage to prepare a single statment, that means everything
-  // in the SQL was treated as a comment.
-  if (!prev_stmt)
-    return base::ErrStatus("No valid SQL to run");
-
-  // Update the output statment and column count.
-  *output_stmt = std::move(prev_stmt);
-  metadata->column_count =
-      static_cast<uint32_t>(sqlite3_column_count(output_stmt->get()));
-  return base::OkStatus();
-}
-
 const char* TraceTypeToString(TraceType trace_type) {
   switch (trace_type) {
     case kUnknownTraceType:
@@ -454,8 +327,8 @@
 }
 
 // Register SQL functions only used in local development instances.
-void RegisterDevFunctions(sqlite3* db) {
-  RegisterFunction<WriteFile>(db, "WRITE_FILE", 2);
+void RegisterDevFunctions(PerfettoSqlEngine* engine) {
+  RegisterFunction<WriteFile>(engine, "WRITE_FILE", 2);
 }
 
 sql_modules::NameToModule GetStdlibModules() {
@@ -511,63 +384,85 @@
     context_.content_analyzer.reset(new ProtoContentAnalyzer(&context_));
   }
 
-  sqlite3_str_split_init(engine_.db());
+  sqlite3_str_split_init(engine_.sqlite_engine()->db());
   RegisterAdditionalModules(&context_);
-  InitializePreludeTablesViews(engine_.db());
 
   // New style function registration.
   if (cfg.enable_dev_features) {
-    RegisterDevFunctions(engine_.db());
+    RegisterDevFunctions(&engine_);
   }
-  RegisterFunction<Glob>(engine_.db(), "glob", 2);
-  RegisterFunction<Hash>(engine_.db(), "HASH", -1);
-  RegisterFunction<Base64Encode>(engine_.db(), "BASE64_ENCODE", 1);
-  RegisterFunction<Demangle>(engine_.db(), "DEMANGLE", 1);
-  RegisterFunction<SourceGeq>(engine_.db(), "SOURCE_GEQ", -1);
-  RegisterFunction<ExportJson>(engine_.db(), "EXPORT_JSON", 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_.db(), "EXTRACT_ARG", 2,
+  RegisterFunction<ExtractArg>(&engine_, "EXTRACT_ARG", 2,
                                context_.storage.get());
-  RegisterFunction<AbsTimeStr>(engine_.db(), "ABS_TIME_STR", 1,
+  RegisterFunction<AbsTimeStr>(&engine_, "ABS_TIME_STR", 1,
                                context_.clock_converter.get());
-  RegisterFunction<ToMonotonic>(engine_.db(), "TO_MONOTONIC", 1,
+  RegisterFunction<ToMonotonic>(&engine_, "TO_MONOTONIC", 1,
                                 context_.clock_converter.get());
-  RegisterFunction<CreateFunction>(
-      engine_.db(), "CREATE_FUNCTION", 3,
-      std::unique_ptr<CreateFunction::Context>(
-          new CreateFunction::Context{engine_.db(), &create_function_state_}));
+  RegisterFunction<CreateFunction>(&engine_, "CREATE_FUNCTION", 3, &engine_);
   RegisterFunction<CreateViewFunction>(
-      engine_.db(), "CREATE_VIEW_FUNCTION", 3,
+      &engine_, "CREATE_VIEW_FUNCTION", 3,
       std::unique_ptr<CreateViewFunction::Context>(
-          new CreateViewFunction::Context{engine_.db()}));
-  RegisterFunction<Import>(engine_.db(), "IMPORT", 1,
-                           std::unique_ptr<Import::Context>(new Import::Context{
-                               engine_.db(), this, &sql_modules_}));
+          new CreateViewFunction::Context{engine_.sqlite_engine()->db()}));
+  RegisterFunction<Import>(
+      &engine_, "IMPORT", 1,
+      std::unique_ptr<Import::Context>(new Import::Context{
+          engine_.sqlite_engine()->db(), this, &sql_modules_}));
   RegisterFunction<ToFtrace>(
-      engine_.db(), "TO_FTRACE", 1,
+      &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(engine_.db());
-  RegisterValueAtMaxTsFunction(engine_.db());
+  RegisterLastNonNullFunction(engine_.sqlite_engine()->db());
+  RegisterValueAtMaxTsFunction(engine_.sqlite_engine()->db());
   {
-    base::Status status = RegisterStackFunctions(engine_.db(), &context_);
+    base::Status status = RegisterStackFunctions(&engine_, &context_);
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
   {
-    base::Status status = PprofFunctions::Register(engine_.db(), &context_);
+    base::Status status =
+        PprofFunctions::Register(engine_.sqlite_engine()->db(), &context_);
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
   {
-    base::Status status = LayoutFunctions::Register(engine_.db(), &context_);
+    base::Status status =
+        LayoutFunctions::Register(engine_.sqlite_engine()->db(), &context_);
     if (!status.ok())
       PERFETTO_ELOG("%s", status.c_message());
   }
+  {
+    base::Status status = RegisterMathFunctions(engine_);
+    if (!status.ok())
+      PERFETTO_ELOG("%s", status.c_message());
+  }
+
+  const TraceStorage* storage = context_.storage.get();
+
+  // Operator tables.
+  engine_.sqlite_engine()->RegisterVirtualTableModule<SpanJoinOperatorTable>(
+      "span_join", storage, SqliteTable::TableType::kExplicitCreate, false);
+  engine_.sqlite_engine()->RegisterVirtualTableModule<SpanJoinOperatorTable>(
+      "span_left_join", storage, SqliteTable::TableType::kExplicitCreate,
+      false);
+  engine_.sqlite_engine()->RegisterVirtualTableModule<SpanJoinOperatorTable>(
+      "span_outer_join", storage, SqliteTable::TableType::kExplicitCreate,
+      false);
+  engine_.sqlite_engine()->RegisterVirtualTableModule<WindowOperatorTable>(
+      "window", storage, SqliteTable::TableType::kExplicitCreate, true);
+  RegisterCreateViewFunctionModule(engine_.sqlite_engine());
+
+  // Initalize the tables and views in the prelude.
+  InitializePreludeTablesViews(engine_.sqlite_engine()->db());
 
   auto stdlib_modules = GetStdlibModules();
   for (auto module_it = stdlib_modules.GetIterator(); module_it; ++module_it) {
@@ -577,18 +472,13 @@
       PERFETTO_ELOG("%s", status.c_message());
   }
 
-  SetupMetrics(this, engine_.db(), &sql_metrics_,
-               cfg.skip_builtin_metric_paths);
+  SetupMetrics(this, &engine_, &sql_metrics_, cfg.skip_builtin_metric_paths);
 
-  const TraceStorage* storage = context_.storage.get();
-
-  SqlStatsTable::RegisterTable(engine_.db(), storage);
-  StatsTable::RegisterTable(engine_.db(), storage);
-
-  // Operator tables.
-  SpanJoinOperatorTable::RegisterTable(engine_.db(), storage);
-  WindowOperatorTable::RegisterTable(engine_.db(), storage);
-  CreateViewFunction::RegisterTable(engine_.db());
+  // Legacy tables.
+  engine_.sqlite_engine()->RegisterVirtualTableModule<SqlStatsTable>(
+      "sqlstats", storage, SqliteTable::TableType::kEponymousOnly, false);
+  engine_.sqlite_engine()->RegisterVirtualTableModule<StatsTable>(
+      "stats", storage, SqliteTable::TableType::kEponymousOnly, false);
 
   // Tables dynamically generated at query time.
   RegisterTableFunction(std::unique_ptr<ExperimentalFlamegraph>(
@@ -640,6 +530,7 @@
   RegisterDbTable(storage->flow_table());
   RegisterDbTable(storage->slice_table());
   RegisterDbTable(storage->sched_slice_table());
+  RegisterDbTable(storage->spurious_sched_wakeup_table());
   RegisterDbTable(storage->thread_state_table());
   RegisterDbTable(storage->gpu_slice_table());
 
@@ -733,7 +624,8 @@
       context_.storage->InternString(TraceTypeToString(context_.trace_type));
   context_.metadata_tracker->SetMetadata(metadata::trace_type,
                                          Variadic::String(trace_type_id));
-  BuildBoundsTable(engine_.db(), context_.storage->GetTraceTimestampBoundsNs());
+  BuildBoundsTable(engine_.sqlite_engine()->db(),
+                   context_.storage->GetTraceTimestampBoundsNs());
 }
 
 void TraceProcessorImpl::NotifyEndOfFile() {
@@ -771,7 +663,8 @@
   // 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(engine_.db(), context_.storage->GetTraceTimestampBoundsNs());
+  BuildBoundsTable(engine_.sqlite_engine()->db(),
+                   context_.storage->GetTraceTimestampBoundsNs());
 
   TraceProcessorStorageImpl::DestroyContext();
 }
@@ -815,23 +708,18 @@
       context_.storage->mutable_sql_stats()->RecordQueryBegin(
           sql, base::GetWallTimeNs().count());
 
-  ScopedStmt stmt;
-  IteratorImpl::StmtMetadata metadata;
-  base::Status status =
-      PrepareAndStepUntilLastValidStmt(engine_.db(), sql, &stmt, &metadata);
-  PERFETTO_DCHECK((status.ok() && stmt) || (!status.ok() && !stmt));
-
+  base::StatusOr<PerfettoSqlEngine::ExecutionResult> result =
+      engine_.ExecuteUntilLastStatement(sql);
   std::unique_ptr<IteratorImpl> impl(
-      new IteratorImpl(this, engine_.db(), status, std::move(stmt),
-                       std::move(metadata), sql_stats_row));
+      new IteratorImpl(this, std::move(result), sql_stats_row));
   return Iterator(std::move(impl));
 }
 
 void TraceProcessorImpl::InterruptQuery() {
-  if (!engine_.db())
+  if (!engine_.sqlite_engine()->db())
     return;
   query_interrupted_.store(true);
-  sqlite3_interrupt(engine_.db());
+  sqlite3_interrupt(engine_.sqlite_engine()->db());
 }
 
 bool TraceProcessorImpl::IsRootMetricField(const std::string& metric_name) {
@@ -923,7 +811,7 @@
           prev_path.c_str(), path.c_str(), metric.proto_field_name->c_str());
     }
 
-    InsertIntoTraceMetricsTable(engine_.db(), no_ext_name);
+    InsertIntoTraceMetricsTable(engine_.sqlite_engine()->db(), no_ext_name);
   }
 
   sql_metrics_.emplace_back(metric);
@@ -951,7 +839,7 @@
     auto fn_name = desc.full_name().substr(desc.package_name().size() + 1);
     std::replace(fn_name.begin(), fn_name.end(), '.', '_');
     RegisterFunction<metrics::BuildProto>(
-        engine_.db(), 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 88b4b6e..973ec35 100644
--- a/src/trace_processor/trace_processor_impl.h
+++ b/src/trace_processor/trace_processor_impl.h
@@ -34,6 +34,7 @@
 #include "src/trace_processor/prelude/functions/create_view_function.h"
 #include "src/trace_processor/prelude/functions/import.h"
 #include "src/trace_processor/sqlite/db_sqlite_table.h"
+#include "src/trace_processor/sqlite/perfetto_sql_engine.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"
@@ -120,11 +121,7 @@
 
   bool IsRootMetricField(const std::string& metric_name);
 
-  SqliteEngine engine_;
-
-  // 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_;
+  PerfettoSqlEngine engine_;
 
   DescriptorPool pool_;
 
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index da08183..44df9a9 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -763,7 +763,7 @@
                                       builds. The features behind this flag can
                                       break at any time without any warning.
 
-Standard library: 
+Standard library:
  --add-sql-module MODULE_PATH         Files from the directory will be treated
                                       as a new SQL module and can be used for
                                       IMPORT. The name of the directory is the
@@ -1016,10 +1016,11 @@
   return command_line_options;
 }
 
-void ExtendPoolWithBinaryDescriptor(google::protobuf::DescriptorPool& pool,
-                                    const void* data,
-                                    int size,
-                                    std::vector<std::string>& skip_prefixes) {
+void ExtendPoolWithBinaryDescriptor(
+    google::protobuf::DescriptorPool& pool,
+    const void* data,
+    int size,
+    const std::vector<std::string>& skip_prefixes) {
   google::protobuf::FileDescriptorSet desc_set;
   PERFETTO_CHECK(desc_set.ParseFromArray(data, size));
   for (const auto& file_desc : desc_set.file()) {
@@ -1237,7 +1238,8 @@
 }
 
 base::Status LoadMetricExtensionProtos(const std::string& proto_root,
-                                       const std::string& mount_path) {
+                                       const std::string& mount_path,
+                                       google::protobuf::DescriptorPool& pool) {
   if (!base::FileExists(proto_root)) {
     return base::ErrStatus(
         "Directory %s does not exist. Metric extension directory must contain "
@@ -1262,6 +1264,11 @@
       serialized_filedescset.data(),
       static_cast<int>(serialized_filedescset.size()));
 
+  // Extend the pool for any subsequent reflection-based operations
+  // (e.g. output json)
+  ExtendPoolWithBinaryDescriptor(
+      pool, serialized_filedescset.data(),
+      static_cast<int>(serialized_filedescset.size()), {});
   RETURN_IF_ERROR(g_tp->ExtendMetricsProto(serialized_filedescset.data(),
                                            serialized_filedescset.size()));
 
@@ -1293,7 +1300,8 @@
   return base::OkStatus();
 }
 
-base::Status LoadMetricExtension(const MetricExtension& extension) {
+base::Status LoadMetricExtension(const MetricExtension& extension,
+                                 google::protobuf::DescriptorPool& pool) {
   const std::string& disk_path = extension.disk_path();
   const std::string& virtual_path = extension.virtual_path();
 
@@ -1305,8 +1313,8 @@
   // Note: Proto files must be loaded first, because we determine whether an SQL
   // file is a metric or not by checking if the name matches a field of the root
   // TraceMetrics proto.
-  RETURN_IF_ERROR(LoadMetricExtensionProtos(disk_path + "protos/",
-                                            kMetricProtoRoot + virtual_path));
+  RETURN_IF_ERROR(LoadMetricExtensionProtos(
+      disk_path + "protos/", kMetricProtoRoot + virtual_path, pool));
   RETURN_IF_ERROR(LoadMetricExtensionSql(disk_path + "sql/", virtual_path));
 
   return base::OkStatus();
@@ -1585,11 +1593,21 @@
     tp->EnableMetatrace(metatrace_config);
   }
 
+  // Descriptor pool used for printing output as textproto. Building on top of
+  // generated pool so default protos in google.protobuf.descriptor.proto are
+  // available.
+  // For some insane reason, the descriptor pool is not movable so we need to
+  // create it here so we can create references and pass it everywhere.
+  google::protobuf::DescriptorPool pool(
+      google::protobuf::DescriptorPool::generated_pool());
+  RETURN_IF_ERROR(PopulateDescriptorPool(pool, metric_extensions));
+
   // We load all the metric extensions even when --run-metrics arg is not there,
   // because we want the metrics to be available in interactive mode or when
   // used in UI using httpd.
+  // Metric extensions are also used to populate the descriptor pool.
   for (const auto& extension : metric_extensions) {
-    RETURN_IF_ERROR(LoadMetricExtension(extension));
+    RETURN_IF_ERROR(LoadMetricExtension(extension, pool));
   }
 
   base::TimeNanos t_load{};
@@ -1616,14 +1634,6 @@
     RETURN_IF_ERROR(RunQueries(options.pre_metrics_path, false));
   }
 
-  // Descriptor pool used for printing output as textproto. Building on top of
-  // generated pool so default protos in google.protobuf.descriptor.proto are
-  // available.
-  // For some insane reason, the descriptor pool is not movable so we need to
-  // create it here so we can create references and pass it everywhere.
-  google::protobuf::DescriptorPool pool(
-      google::protobuf::DescriptorPool::generated_pool());
-  RETURN_IF_ERROR(PopulateDescriptorPool(pool, metric_extensions));
 
   std::vector<MetricNameAndPath> metrics;
   if (!options.metric_names.empty()) {
diff --git a/src/trace_processor/util/interned_message_view.h b/src/trace_processor/util/interned_message_view.h
index 3422b8b..f6a9a13 100644
--- a/src/trace_processor/util/interned_message_view.h
+++ b/src/trace_processor/util/interned_message_view.h
@@ -71,7 +71,9 @@
     if (PERFETTO_TYPE_IDENTIFIER &&
         strcmp(decoder_type_,
                // GCC complains if this arg can be null.
-               PERFETTO_TYPE_IDENTIFIER ? PERFETTO_TYPE_IDENTIFIER : "") != 0) {
+               static_cast<bool>(PERFETTO_TYPE_IDENTIFIER)
+                   ? PERFETTO_TYPE_IDENTIFIER
+                   : "") != 0) {
       PERFETTO_FATAL(
           "Interning entry accessed under different types! previous type: "
           "%s. new type: %s.",
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 f17f6b6..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,
diff --git a/src/traced/probes/ftrace/proto_translation_table.cc b/src/traced/probes/ftrace/proto_translation_table.cc
index 5b8c09e..9dcde14 100644
--- a/src/traced/probes/ftrace/proto_translation_table.cc
+++ b/src/traced/probes/ftrace/proto_translation_table.cc
@@ -124,7 +124,7 @@
 
   if (!InferFtraceType(ftrace_field.type_and_name, ftrace_field.size,
                        ftrace_field.is_signed, &field->ftrace_type)) {
-    PERFETTO_FATAL(
+    PERFETTO_DFATAL(
         "Failed to infer ftrace field type for \"%s.%s\" (type:\"%s\" "
         "size:%d "
         "signed:%d)",
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/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..df38bda 100644
--- a/src/traced/probes/statsd_client/statsd_binder_data_source.cc
+++ b/src/traced/probes/statsd_client/statsd_binder_data_source.cc
@@ -202,7 +202,7 @@
 
 // static
 const ProbesDataSource::Descriptor StatsdBinderDataSource::descriptor = {
-    /*name*/ "android.statsd_binder",
+    /*name*/ "android.statsd",
     /*flags*/ Descriptor::kFlagsNone,
     /*fill_descriptor_func*/ nullptr,
 };
@@ -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/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 7a15a0a..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>
@@ -304,15 +301,18 @@
 // 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),
@@ -580,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)) {
@@ -603,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);
@@ -610,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
@@ -623,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;
@@ -846,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;
@@ -936,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);
@@ -953,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.
@@ -1238,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;
@@ -1478,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 =
@@ -1537,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();
@@ -1546,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
@@ -1593,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;
@@ -2299,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
@@ -2351,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) {
@@ -3490,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;
@@ -3503,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_));
 
@@ -3562,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;
@@ -3582,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(
@@ -3807,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_);
@@ -3903,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 f98d6ff..d38a776 100644
--- a/src/tracing/core/virtual_destructors.cc
+++ b/src/tracing/core/virtual_destructors.cc
@@ -38,6 +38,6 @@
 
 // TODO(primiano): make pure virtual after various 3way patches.
 void ConsumerEndpoint::CloneSession(TracingSessionID) {}
-void Consumer::OnSessionCloned(bool, const std::string&) {}
+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/trace_processor/db/storage.cc b/src/tracing/core/zlib_compressor.h
similarity index 61%
copy from src/trace_processor/db/storage.cc
copy to src/tracing/core/zlib_compressor.h
index 4799d04..1962c48 100644
--- a/src/trace_processor/db/storage.cc
+++ b/src/tracing/core/zlib_compressor.h
@@ -14,14 +14,20 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/db/storage.h"
+#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 {
-namespace trace_processor {
-namespace column {
 
-Storage::~Storage() = default;
+// Matches TracingServiceImpl::kMaxTracePacketSliceSize. Exposed for testing.
+static constexpr size_t kZlibCompressSliceSize = 128 * 1024 - 512;
 
-}  // namespace column
-}  // namespace trace_processor
+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/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/api_integrationtest.cc b/src/tracing/test/api_integrationtest.cc
index 0755e02..7fdc70c 100644
--- a/src/tracing/test/api_integrationtest.cc
+++ b/src/tracing/test/api_integrationtest.cc
@@ -3472,7 +3472,8 @@
       "foo", perfetto::DynamicString{std::string("Event5")},
       ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END);
   PERFETTO_INTERNAL_TRACK_EVENT(
-      "foo", "Event6", ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END);
+      "foo", perfetto::StaticString{"Event6"},
+      ::perfetto::protos::pbzero::TrackEvent::TYPE_SLICE_END);
 
   auto slices = StopSessionAndReadSlicesFromTrace(tracing_session);
   ASSERT_EQ(6u, slices.size());
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 10d5b0d..a384b57 100644
--- a/test/configs/statsd.cfg
+++ b/test/configs/statsd.cfg
@@ -5,7 +5,7 @@
 
 data_sources {
   config {
-    name: "android.statsd_binder"
+    name: "android.statsd"
     target_buffer: 0
     statsd_tracing_config {
       push_atom_id: ATOM_FLASHLIGHT_STATE_CHANGED
diff --git a/test/cts/reporter/reporter_test_cts.cc b/test/cts/reporter/reporter_test_cts.cc
index f9294f4..8745dcf 100644
--- a/test/cts/reporter/reporter_test_cts.cc
+++ b/test/cts/reporter/reporter_test_cts.cc
@@ -42,6 +42,14 @@
   trace_config.add_buffers()->set_size_kb(1024);
   trace_config.set_duration_ms(200);
   trace_config.set_allow_user_build_tracing(true);
+  trace_config.set_unique_session_name("TestEndToEndReport");
+
+  // Make the trace as small as possible (see b/282508742).
+  auto* builtin = trace_config.mutable_builtin_data_sources();
+  builtin->set_disable_clock_snapshotting(true);
+  builtin->set_disable_system_info(true);
+  builtin->set_disable_service_events(true);
+  builtin->set_disable_chunk_usage_histograms(true);
 
   auto* ds_config = trace_config.add_data_sources()->mutable_config();
   ds_config->set_name("android.perfetto.FakeProducer");
@@ -72,6 +80,7 @@
   auto perfetto_proc = Exec("perfetto",
                             {
                                 "--upload",
+                                "--no-guardrails",
                                 "-c",
                                 "-",
                             },
diff --git a/test/data/chrome_5672_histograms.pftrace.gz.sha256 b/test/data/chrome_5672_histograms.pftrace.gz.sha256
new file mode 100644
index 0000000..5d22333
--- /dev/null
+++ b/test/data/chrome_5672_histograms.pftrace.gz.sha256
@@ -0,0 +1 @@
+a09bd44078ac71bcfbc901b0544750e8344d0d0f6f96e220f700a5a53fa932ee
\ No newline at end of file
diff --git a/test/data/sched_wakeup_trace.atr.sha256 b/test/data/sched_wakeup_trace.atr.sha256
new file mode 100644
index 0000000..496b259
--- /dev/null
+++ b/test/data/sched_wakeup_trace.atr.sha256
@@ -0,0 +1 @@
+ae61181ded60bf214859c2072b90dca49226338901d368e6aea329681bff30db
\ No newline at end of file
diff --git a/test/data/speedometer.perfetto_trace.gz.sha256 b/test/data/speedometer.perfetto_trace.gz.sha256
new file mode 100644
index 0000000..d330773
--- /dev/null
+++ b/test/data/speedometer.perfetto_trace.gz.sha256
@@ -0,0 +1 @@
+8a159b354d74a3ca0d38ce9cd071ef47de322db4261ee266bfafe04d70310529
\ 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 b78630c..71dc5bf 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 @@
-59f5feeab8b216aab64795b4fb620d9386d45769e46cd3b7df12e0b8c85dca9a
\ No newline at end of file
+24a0c7c0bd17908474dda832f51aca06226c44f93043b6b6ff8b698d013818ea
\ 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 39ac1e4..2e2f6fd 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 @@
-f146aec8fe3dff85764c2760a8726e38f085006c1a217aca89b0a847390684cf
\ No newline at end of file
+01576322a83634df3f5d4fee6c7dac5c3835f926b7de752b99e827e5688fec2d
\ 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 cf2b69d..58c3975 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 @@
-a238887636bc86b86e179fc5bc11024cd2b5151797365146b4f94c6de6994280
\ No newline at end of file
+f618d3e05c466f49a4e130b02b1ab597dc8e0d51285c801fa9af3c6e9df505a5
\ 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 29aeeb0..9d7b91c 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 @@
-40026a723cf944adb25668cc6618513271aff14cd7271804b4e208bf6a9817d8
\ No newline at end of file
+86d0bc258ef11ba82a78f4b28a1463fb4b732545b95f2ee8e8a4d6271370a030
\ 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 4af29fe..312b01f 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 @@
-81154f3a88aee01576eba17432269818f1431166cb6071256ee1c35e14851271
\ No newline at end of file
+c8e073f0030da0c6d2dd514eed99a8e0eed79ec75c18e177812de73bf0440cf2
\ 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 2a17dfb..0332663 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 @@
-3ae58540e40e9597d92aa15735e253d14ed9fc87753e543695226fce51481004
\ No newline at end of file
+b841b628cd3908f434d28ad0234656ceaeb4a418659200e2670343d2f885fa68
\ 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 bf8f89b..69e55e1 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 @@
-db689629c3ba9a51e74d48edd3e801bf062dd985ed5210e5a9b4f1bce50f29a7
\ No newline at end of file
+e7dc92a76eec326637bebaa176482197c621f48bc066fc761d0b1d19513c8c21
\ 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 6c30778..67a8dd4 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 @@
-eae8d6c700a16b5062736c54e4fe0ffab569ffd839715138e74cd257fdb014fa
\ No newline at end of file
+2d66039e67f93ea58155d19d3be5aba40d3d6f0053f9d1ada63e47828af28abe
\ 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 5117a65..623260e 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 @@
-5767e81834101bf14dcd1917544f1605b8dbb8aa747a7eefd1c52f4db891575f
\ No newline at end of file
+b51e0a5035eab217bc4339800e8a6c4025bfc7d5240844c152fbc867b28f75ba
\ 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 ff3aafa..679bd39 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 @@
-ab1a1b7c948e008fa5d44a4471dc24977f3ab2d592c1ceeaa0ab4afac5bb2336
\ No newline at end of file
+99d3e463fe3812942825829a388bb2d5b11d73010c3213b11d09884dfc1663b5
\ 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 f4e56d2..21fefdd 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 @@
-d7c89c766d8db408de06f79654e2f81d8c761c08c4451991447807468df0f3d5
\ No newline at end of file
+a151d536d2061b45910d894b1735ddc4eac97c4102754d7f1e1b31ebde368675
\ 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 46f0129..849be2d 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 @@
-e596f8037a578f1e58a33bbe08f5d5621b1d37e55456409e5f8799a6897eedb9
\ No newline at end of file
+4b07a28b92a72568615003beb4eb086adc9be581a27baa2ea593c5e88802647b
\ 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 23be44d..8e93988 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 @@
-b938d4dfcc109165feaedb9421276a355d03a383a0d21ab91ff72409b18a3ab5
\ No newline at end of file
+56ec21cedc480d6b1b2a894bc70411c89424386a7910d62a09a6a5f1a5921c15
\ 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 d26043d..df63f69 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 @@
-ef5dfa6588634af3b6ac4950d751f7f8b73eeaae7f74c9e45d65ede4134451fe
\ No newline at end of file
+10931936be5a35ed461fce47ede47225ffc750a85a13462a614366f26160ca11
\ 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 0abbaa7..2afc1a3 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 @@
-d62fb2e2f4067552d4f9d4170b4ffa45c74171b4de750292ded957cb6b12b669
\ No newline at end of file
+04e0762424dae233fb13d5cfff51a2d3075ad62220538010d0ab376fc79022a9
\ 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 ad17707..6272735 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 @@
-a16bc9d707e713ef1ce9110a74790bbbeaf27e1fc7f798152c18e9c81bcedbbf
\ No newline at end of file
+c421aafc09b9ce506fd6eaa1dc358855182cd18704715491f83b7d49828c5826
\ 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 23be44d..8e93988 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 @@
-b938d4dfcc109165feaedb9421276a355d03a383a0d21ab91ff72409b18a3ab5
\ No newline at end of file
+56ec21cedc480d6b1b2a894bc70411c89424386a7910d62a09a6a5f1a5921c15
\ 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 ad17707..6272735 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 @@
-a16bc9d707e713ef1ce9110a74790bbbeaf27e1fc7f798152c18e9c81bcedbbf
\ No newline at end of file
+c421aafc09b9ce506fd6eaa1dc358855182cd18704715491f83b7d49828c5826
\ 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 796c14f..6272735 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 @@
-7c2317088fd3ada6af276ab517e67163b091f7e9bd98e443794397fe58b61ae3
\ No newline at end of file
+c421aafc09b9ce506fd6eaa1dc358855182cd18704715491f83b7d49828c5826
\ 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 4324721..a494f13 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 @@
-9d502e8458f85884c7bf1a6411aa3425c63ccf102e6f537318733704e7a416fb
\ No newline at end of file
+8de36401497e509ab127d202b83a056c40c2f5f461254f1ea79fe7a4861b059e
\ 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 060bb57..cbb5609 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 @@
-da73a7a9d403491d5de58ae013e8b66337c9bf48422ae2ce212ab91bc33a6d5c
\ No newline at end of file
+7b0fb778fef603ab28c5bc8459db420d2c9d6c6a7a3e5b4e6547b3a2fb067249
\ 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 23be44d..8e93988 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 @@
-b938d4dfcc109165feaedb9421276a355d03a383a0d21ab91ff72409b18a3ab5
\ No newline at end of file
+56ec21cedc480d6b1b2a894bc70411c89424386a7910d62a09a6a5f1a5921c15
\ 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 ad17707..6272735 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 @@
-a16bc9d707e713ef1ce9110a74790bbbeaf27e1fc7f798152c18e9c81bcedbbf
\ No newline at end of file
+c421aafc09b9ce506fd6eaa1dc358855182cd18704715491f83b7d49828c5826
\ 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 ad17707..6272735 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 @@
-a16bc9d707e713ef1ce9110a74790bbbeaf27e1fc7f798152c18e9c81bcedbbf
\ No newline at end of file
+c421aafc09b9ce506fd6eaa1dc358855182cd18704715491f83b7d49828c5826
\ 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/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..eb16850
--- /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,"4G (LTE)",3000
+4000,"battery_stats.data_conn",20,"5G (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..c4e2b2b 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,9 +41,10 @@
     process {
       name: "com.google.android.third.process"
       uid: 10003
+      pid: 3000
     }
     ts: 2000000
-    dur: 150000000
+    dur: 180000000
     blocking_calls {
       name: "Contending for pthread mutex"
       cnt: 1
@@ -57,6 +60,20 @@
       min_dur_ms: 10
     }
     blocking_calls {
+      name: "ExpNotRow#onMeasure(BigTextStyle)"
+      cnt: 1
+      total_dur_ms: 10
+      max_dur_ms: 10
+      min_dur_ms: 10
+    }
+    blocking_calls {
+      name: "ExpNotRow#onMeasure(MessagingStyle)"
+      cnt: 1
+      total_dur_ms: 10
+      max_dur_ms: 10
+      min_dur_ms: 10
+    }
+    blocking_calls {
       name: "ImageDecoder#decodeBitmap"
       cnt: 1
       total_dur_ms: 10
@@ -85,6 +102,13 @@
       min_dur_ms: 10
     }
     blocking_calls {
+      name: "NotificationStackScrollLayout#onMeasure"
+      cnt: 1
+      total_dur_ms: 10
+      max_dur_ms: 10
+      min_dur_ms: 10
+    }
+    blocking_calls {
       name: "SuspendThreadByThreadId <...>"
       cnt: 1
       total_dur_ms: 10
@@ -147,6 +171,7 @@
     process {
       name: "com.android.systemui"
       uid: 10001
+      pid: 1000
     }
     ts: 20000000
     dur: 10000000
@@ -164,6 +189,7 @@
     process {
       name: "com.android.systemui"
       uid: 10001
+      pid: 1000
     }
     ts: 22000000
     dur: 10000000
@@ -175,4 +201,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..7ec8cb9 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
@@ -26,11 +26,13 @@
 # List of blocking calls
 blocking_call_names = [
     'monitor contention with something else', 'SuspendThreadByThreadId 123',
-    'LoadApkAssetsFd 123', 'binder transaction',
-    'inflate', 'Lock contention on thread list lock (owner tid: 1665)',
+    'LoadApkAssetsFd 123', 'binder transaction', 'inflate',
+    'Lock contention on thread list lock (owner tid: 1665)',
     'CancellableContinuationImpl#123', 'relayoutWindow*', 'measure', 'layout',
     'configChanged', 'Contending for pthread mutex',
     'ImageDecoder#decodeBitmap', 'ImageDecoder#decodeDrawable',
+    'NotificationStackScrollLayout#onMeasure',
+    'ExpNotRow#onMeasure(MessagingStyle)', 'ExpNotRow#onMeasure(BigTextStyle)',
     'Should not be in the metric'
 ]
 
@@ -45,6 +47,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 +163,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 +220,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_monitor_contention.out b/test/trace_processor/diff_tests/android/android_monitor_contention.out
index 02a6412..8940be9 100644
--- a/test/trace_processor/diff_tests/android/android_monitor_contention.out
+++ b/test/trace_processor/diff_tests/android/android_monitor_contention.out
@@ -12,7 +12,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "PackageManager"
+    blocked_thread_tid: 693
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -39,7 +42,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -66,7 +72,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -93,7 +102,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "batterystats-ha"
+    blocked_thread_tid: 676
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -120,7 +132,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "binder:642_12"
+    blocked_thread_tid: 2720
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     binder_reply_ts: 1737055785896
@@ -154,7 +169,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "StorageUserConn"
+    blocked_thread_tid: 1759
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -171,7 +189,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "StorageUserConn"
+    blocked_thread_tid: 1759
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -208,7 +229,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -225,7 +249,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -242,7 +269,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -259,7 +289,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -276,7 +309,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -293,7 +329,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -310,7 +349,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -327,7 +369,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -344,7 +389,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -361,7 +409,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -378,7 +429,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -395,7 +449,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -412,7 +469,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -429,7 +489,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -446,7 +509,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -463,7 +529,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -480,7 +549,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -497,7 +569,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -514,7 +589,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -531,7 +609,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -548,7 +629,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -565,7 +649,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -582,7 +669,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -599,7 +689,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -616,7 +709,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -633,7 +729,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -650,7 +749,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -667,7 +769,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -684,7 +789,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -701,7 +809,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -718,7 +829,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -735,7 +849,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -752,7 +869,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -769,7 +889,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -786,7 +909,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -803,7 +929,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -820,7 +949,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -837,7 +969,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -854,7 +989,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -871,7 +1009,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -888,7 +1029,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -905,7 +1049,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -922,7 +1069,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -939,7 +1089,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -956,7 +1109,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -973,7 +1129,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -990,7 +1149,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1007,7 +1169,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1024,7 +1189,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1041,14 +1209,12 @@
     waiter_count: 1
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.display"
+    blocked_thread_tid: 663
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "R"
-      thread_state_dur: 122815
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 914
@@ -1063,7 +1229,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1090,7 +1259,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1117,7 +1289,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1144,7 +1319,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "binder:642_11"
+    blocked_thread_tid: 2505
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1182,13 +1360,16 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "binder:642_12"
+    blocked_thread_tid: 2720
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
       thread_state: "R+"
-      thread_state_dur: 7143444
-      thread_state_count: 4
+      thread_state_dur: 7127675
+      thread_state_count: 3
     }
     thread_states {
       thread_state: "Running"
@@ -1210,13 +1391,16 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "binder:642_12"
+    blocked_thread_tid: 2720
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
       thread_state: "R+"
-      thread_state_dur: 7143444
-      thread_state_count: 4
+      thread_state_dur: 7127675
+      thread_state_count: 3
     }
     thread_states {
       thread_state: "Running"
@@ -1237,24 +1421,12 @@
     waiter_count: 1
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "R+"
-      thread_state_dur: 7100855
-      thread_state_count: 1
-    }
-    thread_states {
-      thread_state: "Running"
-      thread_state_dur: 40711
-      thread_state_count: 1
-    }
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 2814860
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 983
@@ -1269,14 +1441,12 @@
     waiter_count: 2
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "binder:642_2"
+    blocked_thread_tid: 658
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 3054413
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 994
@@ -1291,7 +1461,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1308,7 +1481,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1325,7 +1501,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1342,7 +1521,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1359,7 +1541,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1376,7 +1561,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1393,7 +1581,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1410,7 +1601,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1427,7 +1621,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1444,7 +1641,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1461,7 +1661,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1478,7 +1681,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1495,7 +1701,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1512,7 +1721,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1529,7 +1741,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1546,7 +1761,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1563,7 +1781,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1580,7 +1801,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_12"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2720
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -1597,7 +1821,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1624,7 +1851,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1651,7 +1881,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1678,7 +1911,10 @@
     waiter_count: 0
     blocking_thread_name: "batterystats-handler"
     blocked_thread_name: "binder:642_11"
+    blocked_thread_tid: 2505
+    blocking_thread_tid: 676
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1710,7 +1946,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1737,7 +1976,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1764,7 +2006,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1791,7 +2036,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1818,7 +2066,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1845,7 +2096,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1872,7 +2126,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1899,7 +2156,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1926,7 +2186,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1953,7 +2216,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -1980,7 +2246,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -2007,7 +2276,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -2034,7 +2306,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -2061,7 +2336,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -2088,7 +2366,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -2115,7 +2396,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -2142,7 +2426,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -2169,7 +2456,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_11"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 2505
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -2196,7 +2486,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "PowerManagerSer"
+    blocked_thread_tid: 687
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2223,7 +2516,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "PowerManagerSer"
+    blocked_thread_tid: 687
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2245,7 +2541,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "binder:642_2"
+    blocked_thread_tid: 658
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     binder_reply_ts: 1737145697570
@@ -2274,7 +2573,10 @@
     waiter_count: 0
     blocking_thread_name: "Thread-45"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 3486
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -2301,7 +2603,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "batterystats-ha"
+    blocked_thread_tid: 676
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2328,7 +2633,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "batterystats-ha"
+    blocked_thread_tid: 676
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2355,7 +2663,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "PowerManagerSer"
+    blocked_thread_tid: 687
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2382,7 +2693,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "PowerManagerSer"
+    blocked_thread_tid: 687
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2410,7 +2724,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "binder:642_14"
+    blocked_thread_tid: 3485
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -2442,7 +2759,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -2479,7 +2799,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2496,7 +2819,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2513,7 +2839,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2530,7 +2859,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "binder:642_11"
+    blocked_thread_tid: 2505
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     binder_reply_ts: 1737164232343
@@ -2549,7 +2881,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2566,7 +2901,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2583,7 +2921,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2600,7 +2941,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2617,7 +2961,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2634,7 +2981,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2651,7 +3001,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2668,7 +3021,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2685,7 +3041,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2702,7 +3061,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2719,7 +3081,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2736,7 +3101,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2753,7 +3121,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2770,7 +3141,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2787,7 +3161,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2804,7 +3181,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2821,7 +3201,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2838,7 +3221,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -2855,7 +3241,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "batterystats-ha"
+    blocked_thread_tid: 676
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2882,7 +3271,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2909,7 +3301,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2936,7 +3331,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2963,7 +3361,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -2990,7 +3391,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -3017,7 +3421,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -3044,7 +3451,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -3071,7 +3481,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -3098,7 +3511,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -3125,7 +3541,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -3152,7 +3571,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -3179,7 +3601,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -3206,7 +3631,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3233,7 +3661,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3260,7 +3691,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3287,7 +3721,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "binder:642_11"
+    blocked_thread_tid: 2505
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     binder_reply_ts: 1737183173575
@@ -3316,7 +3753,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "binder:642_14"
+    blocked_thread_tid: 3485
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3343,7 +3783,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3370,7 +3813,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3397,7 +3843,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3424,7 +3873,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3451,7 +3903,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3478,7 +3933,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3505,7 +3963,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3532,7 +3993,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3559,7 +4023,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3586,7 +4053,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3613,7 +4083,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3640,7 +4113,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3667,7 +4143,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3694,7 +4173,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3721,7 +4203,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3748,7 +4233,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3775,7 +4263,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3802,7 +4293,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_14"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 3485
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3829,7 +4323,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3856,7 +4353,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3883,7 +4383,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3910,7 +4413,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "binder:642_12"
+    blocked_thread_tid: 2720
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -3947,7 +4453,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -3964,14 +4473,12 @@
     waiter_count: 1
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "binder:642_2"
+    blocked_thread_tid: 658
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 3567249
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 1778
@@ -3986,14 +4493,12 @@
     waiter_count: 2
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "binder:642_A"
+    blocked_thread_tid: 1675
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 8523903
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 1786
@@ -4008,14 +4513,12 @@
     waiter_count: 3
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "binder:642_8"
+    blocked_thread_tid: 1548
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 9607080
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 1819
@@ -4030,7 +4533,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_F"
     blocked_thread_name: "binder:642_1"
+    blocked_thread_tid: 657
+    blocking_thread_tid: 2029
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4062,14 +4568,12 @@
     waiter_count: 4
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "binder:642_13"
+    blocked_thread_tid: 2721
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 10733654
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 1825
@@ -4084,7 +4588,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4101,7 +4608,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4118,7 +4628,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4135,7 +4648,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4152,7 +4668,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4169,7 +4688,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4186,7 +4708,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4203,7 +4728,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4220,7 +4748,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4237,7 +4768,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4254,7 +4788,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4271,7 +4808,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4288,7 +4828,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4305,7 +4848,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4322,7 +4868,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4339,7 +4888,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -4356,7 +4908,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "binder:642_12"
+    blocked_thread_tid: 2720
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4383,7 +4938,10 @@
     waiter_count: 0
     blocking_thread_name: "android.ui"
     blocked_thread_name: "binder:642_12"
+    blocked_thread_tid: 2720
+    blocking_thread_tid: 661
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4405,7 +4963,10 @@
     waiter_count: 0
     blocking_thread_name: "android.ui"
     blocked_thread_name: "binder:642_12"
+    blocked_thread_tid: 2720
+    blocking_thread_tid: 661
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4432,7 +4993,10 @@
     waiter_count: 0
     blocking_thread_name: "android.ui"
     blocked_thread_name: "binder:642_2"
+    blocked_thread_tid: 658
+    blocking_thread_tid: 661
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4459,7 +5023,10 @@
     waiter_count: 0
     blocking_thread_name: "android.ui"
     blocked_thread_name: "binder:642_A"
+    blocked_thread_tid: 1675
+    blocking_thread_tid: 661
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4481,7 +5048,10 @@
     waiter_count: 0
     blocking_thread_name: "android.ui"
     blocked_thread_name: "binder:642_8"
+    blocked_thread_tid: 1548
+    blocking_thread_tid: 661
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4508,7 +5078,10 @@
     waiter_count: 0
     blocking_thread_name: "android.ui"
     blocked_thread_name: "binder:642_13"
+    blocked_thread_tid: 2721
+    blocking_thread_tid: 661
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4530,7 +5103,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_13"
     blocked_thread_name: "binder:642_12"
+    blocked_thread_tid: 2720
+    blocking_thread_tid: 2721
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     binder_reply_ts: 1737229638872
@@ -4564,7 +5140,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_13"
     blocked_thread_name: "binder:642_8"
+    blocked_thread_tid: 1548
+    blocking_thread_tid: 2721
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4591,29 +5170,12 @@
     waiter_count: 1
     blocking_thread_name: "binder:642_13"
     blocked_thread_name: "binder:642_A"
+    blocked_thread_tid: 1675
+    blocking_thread_tid: 2721
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "R"
-      thread_state_dur: 59191
-      thread_state_count: 1
-    }
-    thread_states {
-      thread_state: "R+"
-      thread_state_dur: 223496
-      thread_state_count: 1
-    }
-    thread_states {
-      thread_state: "Running"
-      thread_state_dur: 36279
-      thread_state_count: 2
-    }
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 583889
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 2289
@@ -4628,14 +5190,12 @@
     waiter_count: 2
     blocking_thread_name: "binder:642_13"
     blocked_thread_name: "binder:642_E"
+    blocked_thread_tid: 1934
+    blocking_thread_tid: 2721
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 1514707
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 2352
@@ -4650,7 +5210,10 @@
     waiter_count: 0
     blocking_thread_name: "android.ui"
     blocked_thread_name: "binder:642_8"
+    blocked_thread_tid: 1548
+    blocking_thread_tid: 661
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4672,7 +5235,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_A"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1675
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4699,7 +5265,10 @@
     waiter_count: 0
     blocking_thread_name: "android.ui"
     blocked_thread_name: "binder:642_E"
+    blocked_thread_tid: 1934
+    blocking_thread_tid: 661
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4721,7 +5290,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -4743,7 +5315,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -4770,7 +5345,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -4797,7 +5375,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -4824,7 +5405,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -4851,7 +5435,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -4878,7 +5465,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -4905,7 +5495,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -4932,7 +5525,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -4959,7 +5555,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -4986,7 +5585,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -5013,7 +5615,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -5040,7 +5645,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -5067,7 +5675,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -5094,7 +5705,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -5121,7 +5735,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -5148,7 +5765,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -5175,7 +5795,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5212,7 +5835,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
   }
@@ -5229,7 +5855,10 @@
     waiter_count: 0
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 672
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5256,7 +5885,10 @@
     waiter_count: 0
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5283,7 +5915,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "tworkPolicy.uid"
+    blocked_thread_tid: 1193
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5310,7 +5945,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5337,7 +5975,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5359,7 +6000,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5386,7 +6030,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5413,7 +6060,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5440,7 +6090,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5467,7 +6120,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5494,7 +6150,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5521,7 +6180,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5548,7 +6210,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5575,7 +6240,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5602,7 +6270,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5629,7 +6300,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5656,7 +6330,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5683,7 +6360,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5710,7 +6390,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5737,7 +6420,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5764,7 +6450,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5791,7 +6480,10 @@
     waiter_count: 0
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5818,7 +6510,10 @@
     waiter_count: 0
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "PackageManager"
+    blocked_thread_tid: 693
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5845,7 +6540,10 @@
     waiter_count: 0
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "tworkPolicy.uid"
+    blocked_thread_tid: 1193
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5872,7 +6570,10 @@
     waiter_count: 0
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5899,7 +6600,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -5926,7 +6630,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 670
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -5953,7 +6660,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "batterystats-ha"
+    blocked_thread_tid: 676
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -5980,7 +6690,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "batterystats-ha"
+    blocked_thread_tid: 676
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6007,7 +6720,10 @@
     waiter_count: 0
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "android.fg"
+    blocked_thread_tid: 660
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6034,7 +6750,10 @@
     waiter_count: 0
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -6076,31 +6795,14 @@
     waiter_count: 1
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "binder:642_1"
+    blocked_thread_tid: 657
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     binder_reply_ts: 1739927686578
     binder_reply_tid: 657
-    thread_states {
-      thread_state: "R"
-      thread_state_dur: 819671
-      thread_state_count: 3
-    }
-    thread_states {
-      thread_state: "R+"
-      thread_state_dur: 600233
-      thread_state_count: 2
-    }
-    thread_states {
-      thread_state: "Running"
-      thread_state_dur: 890957
-      thread_state_count: 5
-    }
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 623113
-      thread_state_count: 2
-    }
   }
   node {
     node_id: 13948
@@ -6115,7 +6817,10 @@
     waiter_count: 2
     blocking_thread_name: "StorageManagerService"
     blocked_thread_name: "binder:642_E"
+    blocked_thread_tid: 1934
+    blocking_thread_tid: 743
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     binder_reply_ts: 1739931677940
@@ -6134,19 +6839,12 @@
     waiter_count: 1
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "StorageManagerS"
+    blocked_thread_tid: 743
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "Running"
-      thread_state_dur: 480064
-      thread_state_count: 1
-    }
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 1685676
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 13979
@@ -6161,7 +6859,10 @@
     waiter_count: 2
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
   }
@@ -6178,7 +6879,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "PowerManagerSer"
+    blocked_thread_tid: 687
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6200,7 +6904,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6227,7 +6934,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "PackageManager"
+    blocked_thread_tid: 693
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6254,7 +6964,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6281,7 +6994,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6308,7 +7024,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "binder:642_1"
+    blocked_thread_tid: 657
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6335,7 +7054,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6352,7 +7074,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6369,7 +7094,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6386,21 +7114,14 @@
     waiter_count: 1
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "binder:642_13"
+    blocked_thread_tid: 2721
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     binder_reply_ts: 1739956996641
     binder_reply_tid: 2721
-    thread_states {
-      thread_state: "R"
-      thread_state_dur: 70662
-      thread_state_count: 1
-    }
-    thread_states {
-      thread_state: "Running"
-      thread_state_dur: 92726
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 14154
@@ -6415,7 +7136,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6432,7 +7156,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6449,7 +7176,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6466,7 +7196,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6483,7 +7216,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6500,7 +7236,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6517,7 +7256,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6534,7 +7276,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6551,7 +7296,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6568,7 +7316,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6585,7 +7336,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -6617,7 +7371,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "batterystats-ha"
+    blocked_thread_tid: 676
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6634,7 +7391,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "batterystats-ha"
+    blocked_thread_tid: 676
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6651,7 +7411,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "PowerManagerSer"
+    blocked_thread_tid: 687
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6668,7 +7431,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "PowerManagerSer"
+    blocked_thread_tid: 687
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -6685,7 +7451,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "fg"
+    blocked_thread_tid: 3516
+    blocking_thread_tid: 3519
     process_name: "com.android.providers.media.module"
+    pid: 3487
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6712,7 +7481,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "binder:642_E"
+    blocked_thread_tid: 1934
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     binder_reply_ts: 1739981897430
@@ -6741,29 +7513,12 @@
     waiter_count: 1
     blocking_thread_name: "main"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
-    thread_states {
-      thread_state: "R"
-      thread_state_dur: 105075
-      thread_state_count: 1
-    }
-    thread_states {
-      thread_state: "R+"
-      thread_state_dur: 69326
-      thread_state_count: 1
-    }
-    thread_states {
-      thread_state: "Running"
-      thread_state_dur: 722485
-      thread_state_count: 3
-    }
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 267110
-      thread_state_count: 2
-    }
   }
   node {
     node_id: 14623
@@ -6778,14 +7533,12 @@
     waiter_count: 2
     blocking_thread_name: "main"
     blocked_thread_name: "StorageManagerS"
+    blocked_thread_tid: 743
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
-    thread_states {
-      thread_state: "S"
-      thread_state_dur: 1129545
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 14626
@@ -6800,7 +7553,10 @@
     waiter_count: 3
     blocking_thread_name: "main"
     blocked_thread_name: "binder:642_13"
+    blocked_thread_tid: 2721
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     binder_reply_ts: 1739982622780
@@ -6819,14 +7575,12 @@
     waiter_count: 3
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "Running"
-      thread_state_dur: 388360
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 14730
@@ -6841,7 +7595,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "fg"
+    blocked_thread_tid: 3516
+    blocking_thread_tid: 3487
     process_name: "com.android.providers.media.module"
+    pid: 3487
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -6868,7 +7625,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "fg"
+    blocked_thread_tid: 3516
+    blocking_thread_tid: 3519
     process_name: "com.android.providers.media.module"
+    pid: 3487
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6895,7 +7655,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_13"
     blocked_thread_name: "system_server"
+    blocked_thread_tid: 642
+    blocking_thread_tid: 2721
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -6922,7 +7685,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "fg"
+    blocked_thread_tid: 3516
+    blocking_thread_tid: 3487
     process_name: "com.android.providers.media.module"
+    pid: 3487
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -6949,7 +7715,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "fg"
+    blocked_thread_tid: 3516
+    blocking_thread_tid: 3519
     process_name: "com.android.providers.media.module"
+    pid: 3487
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -6976,7 +7745,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "binder:642_1"
+    blocked_thread_tid: 657
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     binder_reply_ts: 1740012085111
@@ -7010,7 +7782,10 @@
     waiter_count: 0
     blocking_thread_name: "android.bg"
     blocked_thread_name: "fg"
+    blocked_thread_tid: 3516
+    blocking_thread_tid: 3519
     process_name: "com.android.providers.media.module"
+    pid: 3487
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7037,7 +7812,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "binder:642_13"
+    blocked_thread_tid: 2721
+    blocking_thread_tid: 642
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     binder_reply_ts: 1740024094690
@@ -7066,7 +7844,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "StorageManagerS"
+    blocked_thread_tid: 743
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7098,7 +7879,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -7115,14 +7899,12 @@
     waiter_count: 1
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
-    thread_states {
-      thread_state: "R+"
-      thread_state_dur: 473837
-      thread_state_count: 1
-    }
   }
   node {
     node_id: 15361
@@ -7137,7 +7919,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -7154,7 +7939,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -7171,7 +7959,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_E"
     blocked_thread_name: "tworkPolicy.uid"
+    blocked_thread_tid: 1193
+    blocking_thread_tid: 1934
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
   }
@@ -7188,7 +7979,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "fg"
+    blocked_thread_tid: 3516
+    blocking_thread_tid: 3487
     process_name: "com.android.providers.media.module"
+    pid: 3487
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -7215,7 +8009,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7242,7 +8039,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7269,7 +8069,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7296,7 +8099,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7323,7 +8129,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7350,7 +8159,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7377,7 +8189,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7404,7 +8219,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7431,7 +8249,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7458,7 +8279,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7485,7 +8309,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7512,7 +8339,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7539,7 +8369,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7566,7 +8399,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7593,7 +8429,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7620,7 +8459,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7647,7 +8489,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7674,7 +8519,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7701,7 +8549,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7728,7 +8579,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7755,7 +8609,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7782,7 +8639,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7809,7 +8669,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7836,7 +8699,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7863,7 +8729,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7890,7 +8759,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7917,7 +8789,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7944,7 +8819,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_1"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 657
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -7971,7 +8849,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -7998,7 +8879,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8025,7 +8909,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8052,7 +8939,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8079,7 +8969,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8106,7 +8999,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8133,7 +9029,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8160,7 +9059,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8187,7 +9089,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8214,7 +9119,10 @@
     waiter_count: 0
     blocking_thread_name: "android.ui"
     blocked_thread_name: "android.bg"
+    blocked_thread_tid: 670
+    blocking_thread_tid: 661
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8246,7 +9154,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8273,7 +9184,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "ActivityManager"
+    blocked_thread_tid: 671
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8300,7 +9214,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8327,7 +9244,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8354,7 +9274,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8381,7 +9304,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8408,7 +9334,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8435,7 +9364,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8462,7 +9394,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8489,7 +9424,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8516,7 +9454,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8543,7 +9484,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8570,7 +9514,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8597,7 +9544,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8624,7 +9574,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8651,7 +9604,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8678,7 +9634,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8705,7 +9664,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8732,7 +9694,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8759,7 +9724,10 @@
     waiter_count: 0
     blocking_thread_name: "binder:642_8"
     blocked_thread_name: "android.ui"
+    blocked_thread_tid: 661
+    blocking_thread_tid: 1548
     process_name: "system_server"
+    pid: 642
     is_blocked_thread_main: false
     is_blocking_thread_main: false
     thread_states {
@@ -8786,7 +9754,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8813,7 +9784,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8840,7 +9814,10 @@
     waiter_count: 0
     blocking_thread_name: "sAsyncHandlerThread"
     blocked_thread_name: "d.process.media"
+    blocked_thread_tid: 2003
+    blocking_thread_tid: 2128
     process_name: "android.process.media"
+    pid: 2003
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8867,7 +9844,10 @@
     waiter_count: 0
     blocking_thread_name: "SysUiBg"
     blocked_thread_name: "ndroid.systemui"
+    blocked_thread_tid: 1253
+    blocking_thread_tid: 1331
     process_name: "com.android.systemui"
+    pid: 1253
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8894,7 +9874,10 @@
     waiter_count: 0
     blocking_thread_name: "SysUiBg"
     blocked_thread_name: "ndroid.systemui"
+    blocked_thread_tid: 1253
+    blocking_thread_tid: 1331
     process_name: "com.android.systemui"
+    pid: 1253
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8921,7 +9904,10 @@
     waiter_count: 0
     blocking_thread_name: "SysUiBg"
     blocked_thread_name: "ndroid.systemui"
+    blocked_thread_tid: 1253
+    blocking_thread_tid: 1331
     process_name: "com.android.systemui"
+    pid: 1253
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8948,7 +9934,10 @@
     waiter_count: 0
     blocking_thread_name: "SysUiBg"
     blocked_thread_name: "ndroid.systemui"
+    blocked_thread_tid: 1253
+    blocking_thread_tid: 1331
     process_name: "com.android.systemui"
+    pid: 1253
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -8975,7 +9964,10 @@
     waiter_count: 0
     blocking_thread_name: "SysUiBg"
     blocked_thread_name: "ndroid.systemui"
+    blocked_thread_tid: 1253
+    blocking_thread_tid: 1331
     process_name: "com.android.systemui"
+    pid: 1253
     is_blocked_thread_main: true
     is_blocking_thread_main: false
     thread_states {
@@ -9002,7 +9994,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "SysUiBg"
+    blocked_thread_tid: 1331
+    blocking_thread_tid: 1253
     process_name: "com.android.systemui"
+    pid: 1253
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -9024,7 +10019,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "plugin"
+    blocked_thread_tid: 1341
+    blocking_thread_tid: 1253
     process_name: "com.android.systemui"
+    pid: 1253
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
@@ -9051,7 +10049,10 @@
     waiter_count: 0
     blocking_thread_name: "main"
     blocked_thread_name: "RenderThread"
+    blocked_thread_tid: 1436
+    blocking_thread_tid: 1253
     process_name: "com.android.systemui"
+    pid: 1253
     is_blocked_thread_main: false
     is_blocking_thread_main: true
     thread_states {
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..eca9f86
--- /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,2110,3,350
diff --git a/test/trace_processor/diff_tests/android/tests.py b/test/trace_processor/diff_tests/android/tests.py
index 25190b0..7d683cf 100644
--- a/test/trace_processor/diff_tests/android/tests.py
+++ b/test/trace_processor/diff_tests/android/tests.py
@@ -111,6 +111,172 @@
         """,
         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 3115 (note: doesn't group with above due to name)
+    #   * Also tests that groups form based on (ts+dur), not just start ts.
+    # * 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: 1005
+          network_packet_bundle {
+            ctx {
+              direction: DIR_EGRESS
+              interface: "wlan"
+              uid: 456
+            }
+            total_duration: 100
+            total_packets: 2
+            total_length: 300
+          }
+        }
+        packet {
+          timestamp: 2015
+          network_packet_bundle {
+            ctx {
+              direction: DIR_EGRESS
+              interface: "wlan"
+              uid: 456
+            }
+            total_duration: 100
+            total_packets: 1
+            total_length: 50
+          }
+        }
+        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'),
@@ -285,41 +451,19 @@
         13934,"D",11950576,1
       """))
 
-  def test_monitor_contention_chain_extraction(self):
+  def test_monitor_contention_chain_extraction_parent(self):
     return DiffTestBlueprint(
         trace=DataPath('android_monitor_contention_trace.atr'),
         query="""
       SELECT IMPORT('android.monitor_contention');
-      SELECT
-        IIF(parent_id IS NULL, "", parent_id) AS 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,
-        IIF(binder_reply_id IS NULL, "", binder_reply_id) AS binder_reply_id,
-        IIF(binder_reply_ts IS NULL, "", binder_reply_ts) AS binder_reply_ts,
-        IIF(binder_reply_tid IS NULL, "", binder_reply_tid) AS binder_reply_tid
-      FROM android_monitor_contention_chain
+      SELECT * FROM android_monitor_contention_chain
+        WHERE parent_id IS NOT NULL
       ORDER BY dur DESC
       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","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,"","",""
+        "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","blocking_tid","upid","process_name","id","ts","dur","track_id","is_blocked_thread_main","blocked_thread_tid","is_blocking_thread_main","blocking_thread_tid","binder_reply_id","binder_reply_ts","binder_reply_tid","pid"
+        956,"void com.android.server.am.AppProfiler.collectPssInBackground()","void com.android.server.am.ProcessRecord.setPid(int)","com.android.server.am.AppProfiler.collectPssInBackground","com.android.server.am.ProcessRecord.setPid","AppProfiler.java:514","ProcessRecord.java:596",0,656,"binder:642_12",506,"android.bg",670,250,"system_server",949,1737122781871,7301144,1236,0,2720,0,670,"[NULL]","[NULL]","[NULL]",642
       """))
 
   def test_monitor_contention_metric(self):
diff --git a/test/trace_processor/diff_tests/atrace/tests_general.py b/test/trace_processor/diff_tests/atrace/tests_general.py
deleted file mode 100644
index 59417c0..0000000
--- a/test/trace_processor/diff_tests/atrace/tests_general.py
+++ /dev/null
@@ -1,91 +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 AtraceGeneral(DiffTestModule):
-
-  def test_android_b2b_async_begin_list_slices(self):
-    return DiffTestBlueprint(
-        trace=Path('android_b2b_async_begin.textproto'),
-        query="""
-SELECT ts, dur, name
-FROM slice;
-""",
-        out=Csv("""
-"ts","dur","name"
-1000,30,"multistart"
-1015,45,"multistart"
-1030,20,"multistart"
-"""))
-
-  def test_process_track_slices_android_async_slice(self):
-    return DiffTestBlueprint(
-        trace=Path('android_async_slice.textproto'),
-        query="""
-SELECT
-  ts,
-  dur,
-  pid,
-  slice.name AS slice_name,
-  process_track.name AS track_name
-FROM slice
-JOIN process_track ON slice.track_id = process_track.id
-JOIN process USING (upid);
-""",
-        out=Path('process_track_slices_android_async_slice.out'))
-
-  def test_async_track_atrace_process_track_slices(self):
-    return DiffTestBlueprint(
-        trace=Path('async_track_atrace.py'),
-        query="""
-SELECT
-  ts,
-  dur,
-  pid,
-  slice.name AS slice_name,
-  process_track.name AS track_name
-FROM slice
-JOIN process_track ON slice.track_id = process_track.id
-JOIN process USING (upid);
-""",
-        out=Csv("""
-"ts","dur","pid","slice_name","track_name"
-50,25,1,"ev","track"
-55,15,1,"ev","track"
-60,5,2,"ev","track"
-"""))
-
-  def test_sys_write_and_atrace(self):
-    return DiffTestBlueprint(
-        trace=Path('sys_write_and_atrace.py'),
-        query="""
-SELECT slice.ts, slice.dur, slice.name, slice.depth
-FROM slice
-JOIN thread_track ON (slice.track_id = thread_track.id)
-JOIN thread USING (utid)
-WHERE tid = 42;
-""",
-        out=Csv("""
-"ts","dur","name","depth"
-100,100,"sys_write",0
-300,50,"sys_write",0
-350,300,"test",0
-600,50,"sys_write",1
-"""))
diff --git a/test/trace_processor/diff_tests/camera/tests_general.py b/test/trace_processor/diff_tests/camera/tests_general.py
deleted file mode 100644
index 7277796..0000000
--- a/test/trace_processor/diff_tests/camera/tests_general.py
+++ /dev/null
@@ -1,42 +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 CameraGeneral(DiffTestModule):
-
-  def test_camera_ion_mem_trace_android_camera(self):
-    return DiffTestBlueprint(
-        trace=DataPath('camera-ion-mem-trace'),
-        query=Metric('android_camera'),
-        out=TextProto(r"""
-android_camera {
-  gc_rss_and_dma {
-    min: 47779840.0
-    max: 2529583104.0
-    avg: 1459479416.3297353
-  }
-}
-"""))
-
-  def test_camera_ion_mem_trace_android_camera_unagg(self):
-    return DiffTestBlueprint(
-        trace=DataPath('camera-ion-mem-trace'),
-        query=Metric('android_camera_unagg'),
-        out=Path('camera-ion-mem-trace_android_camera_unagg.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_scroll_jank_v2.out b/test/trace_processor/diff_tests/chrome/chrome_scroll_jank_v2.out
index dae2c44..b13137f 100644
--- a/test/trace_processor/diff_tests/chrome/chrome_scroll_jank_v2.out
+++ b/test/trace_processor/diff_tests/chrome/chrome_scroll_jank_v2.out
@@ -1,4 +1,3 @@
 
 "scroll_processing_ms","scroll_jank_processing_ms","scroll_jank_percentage"
 12374.560000,154.217000,1.246242
-
diff --git a/test/trace_processor/diff_tests/chrome/chrome_speedometer.out b/test/trace_processor/diff_tests/chrome/chrome_speedometer.out
new file mode 100644
index 0000000..2a52485
--- /dev/null
+++ b/test/trace_processor/diff_tests/chrome/chrome_speedometer.out
@@ -0,0 +1,11 @@
+"iteration","ts","dur","total","mean","geomean","score","num_measurements"
+1,693997310311984,7020297000,5191976000,"324498500.0","254177934.4","78.7",96
+2,694004414619984,6308034000,4600497000,"287531062.5","224382472.1","89.1",96
+3,694010770005984,5878289000,4209484000,"263092750.0","200261720.2","99.9",96
+4,694016699502984,5934578000,4213632000,"263352000.0","201163561.9","99.4",96
+5,694022683560984,5952163000,4259111000,"266194437.5","203014932.4","98.5",96
+6,694028690570984,5966530000,4269728000,"266858000.0","204306068.2","97.9",96
+7,694034719276984,5853043000,4219351000,"263709437.5","200358118.8","99.8",96
+8,694040637173984,6087435000,4261576000,"266348500.0","202962356.2","98.5",96
+9,694046772284984,6040820000,4245060000,"265316250.0","199331263.1","100.3",96
+10,694052857814984,6063770000,4388487000,"274280437.5","208004176.2","96.2",96
diff --git a/test/trace_processor/diff_tests/chrome/chrome_speedometer_test.sql b/test/trace_processor/diff_tests/chrome/chrome_speedometer_test.sql
new file mode 100644
index 0000000..374d72e
--- /dev/null
+++ b/test/trace_processor/diff_tests/chrome/chrome_speedometer_test.sql
@@ -0,0 +1,20 @@
+SELECT IMPORT('chrome.speedometer');
+
+SELECT
+  iteration,
+  ts,
+  dur,
+  total,
+  format('%.1f', mean) AS mean,
+  format('%.1f', geomean) AS geomean,
+  format('%.1f', score) AS score,
+  num_measurements
+FROM
+  chrome_speedometer_iteration,
+  (
+    SELECT iteration, COUNT(*) AS num_measurements
+    FROM chrome_speedometer_measure
+    GROUP BY iteration
+  )
+USING (iteration)
+ORDER BY iteration;
diff --git a/test/trace_processor/diff_tests/chrome/chrome_tasks.out b/test/trace_processor/diff_tests/chrome/chrome_tasks.out
index ef33837..7a75cec 100644
--- a/test/trace_processor/diff_tests/chrome/chrome_tasks.out
+++ b/test/trace_processor/diff_tests/chrome/chrome_tasks.out
@@ -1,5 +1,5 @@
 
-"full_name","task_type","count"
+"name","task_type","count"
 "OnLibevent","other",2208
 "RunTask(posted_from=components/favicon/core/large_icon_worker.cc:OnIconLookupComplete)","scheduler",694
 "RunTask(posted_from=components/history/core/browser/history_service.cc:GetLargestFaviconForURL)","scheduler",694
@@ -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,177 @@
 "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
+"RunTask(posted_from=cc/tiles/tile_manager.cc:TaskSetFinished)","scheduler",30
+"RunTask(posted_from=gpu/command_buffer/service/scheduler.cc:TryScheduleSequence)","scheduler",29
+"viz.mojom.CompositorFrameSinkClient message (hash=50871626)","mojo",29
+"network.mojom.URLLoaderFactory message (hash=4026588969)","mojo",28
+"tracing.mojom.ProducerHost message (hash=1567334432)","mojo",27
+"RunTask(posted_from=services/service_manager/public/cpp/interface_binder.h:BindInterface)","scheduler",26
+"RunTask(posted_from=base/memory/memory_pressure_listener.cc:Notify)","scheduler",25
+"RunTask(posted_from=net/http/http_cache.cc:ProcessQueuedTransactions)","scheduler",25
+"Choreographer(java_views=ToolbarLayout,ToolbarPhone.updateLocationBarLayoutForExpansionAnimation)","choreographer",24
+"tracing.mojom.TracingSessionHost message (hash=167101205)","mojo",24
+"tracing.mojom.TracingSessionHost reply (hash=167101205)","mojo",24
+"RunTask(posted_from=components/history/core/browser/history_service.cc:ScheduleTask)","scheduler",23
+"RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:DoomEntryInternal)","scheduler",23
+"RunTask(posted_from=components/omnibox/browser/history_url_provider.cc:ExecuteWithDB)","scheduler",21
+"content.mojom.ChildProcessHost message (hash=3455726814)","mojo",21
+"Looper.dispatch: android.app.ActivityThread$H(null)","other",20
+"RunTask(posted_from=cc/base/unique_notifier.cc:Schedule)","scheduler",20
+"RunTask(posted_from=ipc/ipc_mojo_bootstrap.cc:NotifyEndpointOfError)","scheduler",20
+"RunTask(posted_from=mojo/public/cpp/bindings/lib/binder_map_internal.h:BindInterface)","scheduler",19
+"RunTask(posted_from=content/browser/gpu/gpu_process_host.cc:CallOnIO)","scheduler",18
+"RunTask(posted_from=services/metrics/public/cpp/delegating_ukm_recorder.cc:AddEntry)","scheduler",18
+"blink.mojom.BrowserInterfaceBroker message (hash=2708892102)","mojo",17
+"RunTask(posted_from=components/update_client/update_engine.cc:HandleComponentComplete)","scheduler",16
+"RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:EntryOperationComplete)","scheduler",16
+"RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:ReturnEntryToCallerAsync)","scheduler",16
+"tracing.mojom.ProducerClient message (hash=1884113734)","mojo",16
+"RunTask(posted_from=cc/tiles/tile_manager.cc:RunOnWorkerThread)","scheduler",15
+"RunTask(posted_from=components/update_client/component.cc:DoHandle)","scheduler",15
+"RunTask(posted_from=gpu/ipc/service/gpu_channel_manager.cc:ScheduleWakeUpGpu)","scheduler",15
+"RunTask(posted_from=third_party/blink/renderer/controller/memory_usage_monitor.cc:StartMonitoringIfNeeded)","scheduler",15
+"content.mojom.FrameHost message (hash=3826696652)","mojo",15
+"tracing.mojom.ProducerClient reply (hash=1377057286)","mojo",15
+"RunTask(posted_from=components/crash/content/browser/crash_metrics_reporter_android.cc:NotifyObservers)","scheduler",14
+"RunTask(posted_from=components/update_client/component.cc:EndState)","scheduler",14
+"RunTask(posted_from=components/viz/service/display_embedder/skia_output_surface_dependency_impl.cc:PostTaskToClientThread)","scheduler",14
+"RunTask(posted_from=mojo/public/cpp/bindings/lib/interface_endpoint_client.cc:SendMessageWithResponder)","scheduler",14
+"RunTask(posted_from=third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc:PostTaskToMainThread)","scheduler",14
+"viz.mojom.GpuHost message (hash=1989238374)","mojo",14
+"RunTask(posted_from=base/allocator/partition_alloc_support.cc:RunMemoryReclaimer)","scheduler",13
+"RunTask(posted_from=cc/trees/proxy_main.cc:SendCommitRequestToImplThreadIfNeeded)","scheduler",13
+"RunTask(posted_from=components/viz/service/display/display_scheduler.cc:ScheduleBeginFrameDeadline)","scheduler",13
+"RunTask(posted_from=content/browser/webui/web_ui_url_loader_factory.cc:DataAvailable)","scheduler",13
+"RunTask(posted_from=gin/v8_platform.cc:PostJob)","scheduler",13
+"RunTask(posted_from=third_party/blink/renderer/platform/loader/fetch/url_loader/web_resource_request_sender.cc:DeletePendingRequest)","scheduler",13
+"SingleThreadProxy::BeginMainFrame(java_views=)","ui_thread_begin_main_frame",13
+"blink.mojom.WidgetInputHandler message (hash=1243813610)","mojo",13
+"content.mojom.FrameHost message (hash=171518470)","mojo",13
+"tracing.mojom.ProducerClient message (hash=1377057286)","mojo",13
+"RunTask(posted_from=components/offline_pages/task/task_queue.cc:StartTaskIfAvailable)","scheduler",12
+"RunTask(posted_from=components/page_load_metrics/renderer/page_timing_metrics_sender.cc:EnsureSendTimer)","scheduler",12
+"RunTask(posted_from=content/browser/webui/web_ui_data_source_impl.cc:GetDataResourceBytesOnWorkerThread)","scheduler",12
+"RunTask(posted_from=net/spdy/spdy_session.cc:MaybePostWriteLoop)","scheduler",12
+"RunTask(posted_from=third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc:UpdatePolicyLocked)","scheduler",12
+"page_load_metrics.mojom.PageLoadMetrics message (hash=1589948206)","mojo",12
+"viz.mojom.CompositorFrameSink message (hash=1797539858)","mojo",12
+"viz.mojom.CompositorFrameSinkClient message (hash=713406245)","mojo",12
+"RunTask(posted_from=ipc/ipc_channel_mojo.cc:SendMessage)","scheduler",11
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:OnAssociatedInterfaceRequest)","scheduler",11
+"blink.mojom.LocalMainFrameHost message (hash=2681895571)","mojo",11
+"Looper.dispatch: android.view.ViewRootImpl$ViewRootHandler(android.widget.Editor$Blink@ef6aa9b)","other",10
+"RunTask(posted_from=components/leveldb_proto/internal/proto_database_impl.h:PostTransaction)","scheduler",10
+"RunTask(posted_from=components/leveldb_proto/internal/proto_leveldb_wrapper.cc:LoadEntriesWithFilter)","scheduler",10
+"RunTask(posted_from=gpu/ipc/service/gpu_channel.cc:CreateCommandBuffer)","scheduler",10
+"RunTask(posted_from=cc/trees/layer_tree_host_impl.cc:DidPresentCompositorFrame)","scheduler",9
+"RunTask(posted_from=content/browser/child_process_launcher_helper.cc:PostLaunchOnLauncherThread)","scheduler",9
+"RunTask(posted_from=content/browser/child_process_launcher_helper.cc:StartLaunchOnClientThread)","scheduler",9
+"RunTask(posted_from=content/browser/child_process_launcher_helper_android.cc:LaunchProcessOnLauncherThread)","scheduler",9
+"RunTask(posted_from=content/browser/tracing/background_tracing_manager_impl.cc:ActivateForProcess)","scheduler",9
+"RunTask(posted_from=mojo/core/node_controller.cc:SendBrokerClientInvitation)","scheduler",9
+"blink.mojom.Widget message (hash=1337806005)","mojo",9
+"content.mojom.ChildProcess message (hash=1868925865)","mojo",9
+"device.mojom.DeviceService message (hash=3372813913)","mojo",9
+"device.mojom.PowerMonitor message (hash=1428246654)","mojo",9
+"memory_instrumentation.mojom.CoordinatorConnector message (hash=3189782928)","mojo",9
+"tracing.mojom.PerfettoService message (hash=3821615380)","mojo",9
+"tracing.mojom.TracedProcess reply (hash=2621133919)","mojo",9
+"tracing.mojom.TracingService message (hash=2713683366)","mojo",9
+"ChildProcessConnection.ChildServiceConnection.onServiceConnected","java",8
+"Looper.dispatch: android.net.ConnectivityManager$CallbackHandler(null)","other",8
+"RunTask(posted_from=base/allocator/partition_alloc_support.cc:RunThreadCachePeriodicPurge)","scheduler",8
+"RunTask(posted_from=chrome/browser/data_saver/data_saver.cc:FetchDataSaverOSSettingAsynchronously)","scheduler",8
+"RunTask(posted_from=gpu/command_buffer/service/gr_cache_controller.cc:ScheduleGrContextCleanup)","scheduler",8
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:AddFilter)","scheduler",8
+"RunTask(posted_from=mojo/public/cpp/bindings/lib/connector.cc:PostDispatchNextMessageFromPipe)","mojo",8
+"RunTask(posted_from=mojo/public/cpp/bindings/lib/connector.cc:StartReceiving)","scheduler",8
+"RunTask(posted_from=net/cert/multi_threaded_cert_verifier.cc:Start)","scheduler",8
+"RunTask(posted_from=net/disk_cache/simple/simple_entry_impl.cc:PostClientCallback)","scheduler",8
+"RunTask(posted_from=ui/events/gesture_detection/gesture_detector.cc:StartTimeout)","scheduler",8
+"blink.mojom.LocalFrameHost message (hash=3716466459)","mojo",8
+"content.mojom.FrameHost message (hash=4063347880)","mojo",8
+"tracing.mojom.ProducerClient message (hash=3526922743)","mojo",8
+"RunTask(posted_from=base/threading/thread.cc:StopSoon)","scheduler",7
+"RunTask(posted_from=components/crash/content/browser/child_process_crash_observer_android.cc:OnChildExit)","scheduler",7
+"RunTask(posted_from=components/viz/service/display_embedder/skia_output_device.cc:FinishSwapBuffers)","scheduler",7
+"RunTask(posted_from=components/viz/service/gl/gpu_service_impl.cc:GetPeakMemoryUsage)","scheduler",7
+"RunTask(posted_from=components/viz/service/gl/gpu_service_impl.cc:GetPeakMemoryUsageOnMainThread)","scheduler",7
+"RunTask(posted_from=components/viz/service/gl/gpu_service_impl.cc:StartPeakMemoryMonitor)","scheduler",7
+"RunTask(posted_from=content/browser/browser_child_process_host_impl.cc:RegisterCoordinatorClient)","scheduler",7
+"RunTask(posted_from=content/browser/service_process_host_impl.cc:Launch)","scheduler",7
+"RunTask(posted_from=content/utility/utility_thread_impl.cc:BindServiceInterface)","scheduler",7
+"RunTask(posted_from=third_party/blink/renderer/core/frame/web_frame_widget_impl.cc:DidSwap)","scheduler",7
+"RunTask(posted_from=third_party/blink/renderer/core/script/module_map.cc:DispatchFinishedNotificationAsync)","scheduler",7
+"RunTask(posted_from=ui/gfx/android/android_surface_control_compat.cc:SetOnCommitCb)","scheduler",7
+"RunTask(posted_from=ui/gfx/android/android_surface_control_compat.cc:SetOnCompleteCb)","scheduler",7
+"content.mojom.ChildProcessHost message (hash=38682745)","mojo",7
+"media_session.mojom.MediaSessionObserver message (hash=3966185760)","mojo",7
+"viz.mojom.GpuService reply (hash=3002350734)","mojo",7
+"ChromeApplication.attachBaseContext","java",6
+"RunTask(posted_from=base/files/important_file_writer.cc:WriteNowWithBackgroundDataProducer)","scheduler",6
+"RunTask(posted_from=base/power_monitor/power_monitor.cc:NotifyPowerStateChange)","scheduler",6
+"RunTask(posted_from=base/task/sequenced_task_runner.h:operator())","scheduler",6
+"RunTask(posted_from=cc/trees/proxy_impl.cc:DrawInternal)","scheduler",6
+"RunTask(posted_from=cc/trees/proxy_impl.cc:~ScopedCommitCompletionEvent)","scheduler",6
+"RunTask(posted_from=cc/trees/single_thread_proxy.cc:DidReceiveCompositorFrameAckOnImplThread)","scheduler",6
+"RunTask(posted_from=chrome/browser/image_decoder/image_decoder.cc:RunDecodeCallbackOnTaskRunner)","scheduler",6
+"RunTask(posted_from=chrome/browser/image_decoder/image_decoder.cc:StartWithOptionsImpl)","scheduler",6
+"RunTask(posted_from=components/history/core/browser/history_service.cc:GetFaviconsForURL)","scheduler",6
+"RunTask(posted_from=components/leveldb_proto/internal/proto_leveldb_wrapper.cc:UpdateEntries)","scheduler",6
+"RunTask(posted_from=components/offline_pages/task/task_queue.cc:TaskCompletedCallback)","scheduler",6
+"RunTask(posted_from=components/omnibox/browser/autocomplete_controller.cc:StartStopTimer)","scheduler",6
+"RunTask(posted_from=components/performance_manager/decorators/page_load_tracker_decorator_helper.cc:NotifyPageLoadTrackerDecoratorOnPMSequence)","scheduler",6
+"RunTask(posted_from=components/performance_manager/performance_manager_impl.cc:CreateNodeImpl)","scheduler",6
+"RunTask(posted_from=components/power_scheduler/power_mode_arbiter.cc:OnTaskRunnerAvailable)","scheduler",6
+"RunTask(posted_from=content/browser/service_worker/service_worker_context_core.cc:NotifyClientIsExecutionReady)","scheduler",6
+"RunTask(posted_from=content/child/child_thread_impl.cc:ExposeInterfacesToBrowser)","scheduler",6
+"RunTask(posted_from=content/child/child_thread_impl.cc:GetBackgroundTracingAgentProvider)","scheduler",6
+"RunTask(posted_from=content/child/child_thread_impl.cc:Init)","scheduler",6
+"RunTask(posted_from=content/common/android/cpu_time_metrics_internal.cc:ProcessCpuTimeMetrics)","scheduler",6
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:Init)","scheduler",6
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:OnChannelConnected)","scheduler",6
+"RunTask(posted_from=mojo/core/node_controller.cc:AcceptBrokerClientInvitation)","scheduler",6
+"RunTask(posted_from=mojo/core/node_controller.cc:Create)","scheduler",6
+"RunTask(posted_from=services/device/public/cpp/power_monitor/power_monitor_broadcast_source.cc:Init)","scheduler",6
+"RunTask(posted_from=services/tracing/public/cpp/perfetto/perfetto_traced_process.cc:CreateProducerConnection)","scheduler",6
+"RunTask(posted_from=services/tracing/public/cpp/perfetto/producer_client.cc:Connect)","scheduler",6
+"RunTask(posted_from=third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc:UpdateForInputEventOnCompositorThread)","scheduler",6
+"blink.mojom.PageBroadcast message (hash=1682824955)","mojo",6
+"network.mojom.NetworkService message (hash=121963099)","mojo",6
+"service_manager.mojom.InterfaceProvider message (hash=607834802)","mojo",6
+"tracing.mojom.ProducerClient message (hash=2964700609)","mojo",6
+"tracing.mojom.TracedProcess message (hash=2621133919)","mojo",6
+"Choreographer(java_views=ToolbarLayout,ToolbarPhone.layoutLocationBar,ToolbarPhone.updateLocationBarLayoutForExpansionAnimation)","choreographer",5
+"Looper.dispatch: android.os.Handler(ZM3@26e2110)","other",5
+"RunTask(posted_from=base/files/important_file_writer.cc:ScheduleWrite)","scheduler",5
+"RunTask(posted_from=cc/trees/proxy_main.cc:UpdateBrowserControlsState)","scheduler",5
+"RunTask(posted_from=components/leveldb_proto/internal/proto_database_impl.h:ParseLoadedEntries)","scheduler",5
+"RunTask(posted_from=components/viz/common/gpu/context_cache_controller.cc:PostIdleCallback)","scheduler",5
+"RunTask(posted_from=gin/v8_foreground_task_runner.cc:PostNonNestableTask)","scheduler",5
+"RunTask(posted_from=ipc/ipc_channel_proxy.cc:Pause)","scheduler",5
+"RunTask(posted_from=net/socket/transport_client_socket_pool.cc:StartBackupJobTimer)","scheduler",5
+"RunTask(posted_from=third_party/blink/renderer/core/css/font_face_set.cc:HandlePendingEventsAndPromisesSoon)","scheduler",5
+"RunTask(posted_from=ui/gl/gl_surface_egl_surface_control.cc:CheckPendingPresentationCallbacks)","scheduler",5
+"blink.mojom.LocalFrameHost message (hash=3721282066)","mojo",5
+"blink.mojom.LocalMainFrame message (hash=2603884585)","mojo",5
+"blink.mojom.LocalMainFrameHost message (hash=2936437485)","mojo",5
+"content.mojom.ChildHistogramFetcherFactory message (hash=3650055636)","mojo",5
+"media_session.mojom.MediaSessionObserver message (hash=169447862)","mojo",5
+"network.mojom.DevToolsObserver message (hash=3093371228)","mojo",5
+"viz.mojom.GpuHost message (hash=2261948180)","mojo",5
+"viz.mojom.GpuHost message (hash=4264056690)","mojo",5
diff --git a/test/trace_processor/diff_tests/chrome/index.py b/test/trace_processor/diff_tests/chrome/index.py
deleted file mode 100644
index 67e8ca4..0000000
--- a/test/trace_processor/diff_tests/chrome/index.py
+++ /dev/null
@@ -1,1427 +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, 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 DiffTestModule_Chrome(DiffTestModule):
-
-  def test_scroll_jank_general_validation(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query=Path('scroll_jank_general_validation_test.sql'),
-        out=Path('scroll_jank_general_validation.out'))
-
-  def test_scroll_jank(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_jank.sql');
-
-SELECT
-  gesture_scroll_id,
-  trace_id,
-  jank,
-  ts,
-  dur,
-  jank_budget
-FROM scroll_jank;
-""",
-        out=Path('scroll_jank.out'))
-
-  def test_event_latency_to_breakdowns(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/event_latency_with_args.perfetto-trace'),
-        query="""
-SELECT RUN_METRIC('chrome/event_latency_to_breakdowns.sql');
-
-SELECT
-  event_latency_ts,
-  event_latency_dur,
-  event_type,
-  GenerationToRendererCompositorNs,
-  GenerationToBrowserMainNs,
-  BrowserMainToRendererCompositorNs,
-  RendererCompositorQueueingDelayNs,
-  unknown_stages_seen
-FROM event_latency_to_breakdowns
-ORDER BY event_latency_id
-LIMIT 30;
-""",
-        out=Path('event_latency_to_breakdowns.out'))
-
-  def test_event_latency_scroll_jank(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/event_latency_with_args.perfetto-trace'),
-        query="""
-SELECT RUN_METRIC('chrome/event_latency_scroll_jank.sql');
-
-SELECT
-  jank,
-  next_jank,
-  prev_jank,
-  gesture_begin_ts,
-  gesture_end_ts,
-  ts,
-  dur,
-  event_type,
-  next_ts,
-  next_dur,
-  prev_ts,
-  prev_dur
-FROM scroll_event_latency_jank
-ORDER BY jank DESC
-LIMIT 10;
-""",
-        out=Path('event_latency_scroll_jank.out'))
-
-  def test_event_latency_scroll_jank_cause(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/event_latency_with_args.perfetto-trace'),
-        query="""
-SELECT RUN_METRIC('chrome/event_latency_scroll_jank_cause.sql');
-
-SELECT
-  dur,
-  ts,
-  event_type,
-  next_jank,
-  prev_jank,
-  next_delta_dur_ns,
-  prev_delta_dur_ns,
-  cause_of_jank,
-  max_delta_dur_ns,
-  sub_cause_of_jank
-FROM event_latency_scroll_jank_cause
-ORDER by ts;
-""",
-        out=Path('event_latency_scroll_jank_cause.out'))
-
-  def test_scroll_flow_event(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_flow_event.sql');
-
-SELECT
-  trace_id,
-  ts,
-  dur,
-  jank,
-  step,
-  ancestor_end,
-  maybe_next_ancestor_ts,
-  next_ts,
-  next_trace_id,
-  next_step
-FROM scroll_flow_event
-ORDER BY gesture_scroll_id, trace_id, ts;
-""",
-        out=Path('scroll_flow_event.out'))
-
-  def test_scroll_flow_event_general_validation(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_flow_event.sql');
-
-SELECT
-  -- Each trace_id (in our example trace not true in general) has 8 steps. There
-  -- are 139 scrolls. So we expect 1112 rows in total 72 of which are janky.
-  (
-    SELECT
-      COUNT(*)
-    FROM (
-      SELECT
-        trace_id,
-        COUNT(*)
-      FROM scroll_flow_event
-      GROUP BY trace_id
-    )
-  ) AS total_scroll_updates,
-  (
-    SELECT COUNT(*) FROM scroll_flow_event
-  ) AS total_flow_event_steps,
-  (
-    SELECT COUNT(*) FROM scroll_flow_event WHERE jank
-  ) AS total_janky_flow_event_steps,
-  (
-    SELECT COUNT(*) FROM (SELECT step FROM scroll_flow_event GROUP BY step)
-  ) AS number_of_unique_steps;
-""",
-        out=Path('scroll_flow_event_general_validation.out'))
-
-  def test_scroll_jank_cause(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_jank_cause.sql');
-
-SELECT
-  COUNT(*) AS total,
-  SUM(jank) AS total_jank,
-  SUM(explained_jank + unexplained_jank) AS sum_explained_and_unexplained,
-  SUM(
-    CASE WHEN explained_jank THEN
-      unexplained_jank
-      ELSE
-        CASE WHEN jank AND NOT unexplained_jank THEN
-          1
-          ELSE
-            0
-        END
-    END
-  ) AS error_rows
-FROM scroll_jank_cause;
-""",
-        out=Csv("""
-"total","total_jank","sum_explained_and_unexplained","error_rows"
-139,7,7,0
-"""))
-
-  def test_scroll_flow_event_queuing_delay(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_flow_event_queuing_delay.sql');
-
-SELECT
-  trace_id,
-  jank,
-  step,
-  next_step,
-  ancestor_end,
-  maybe_next_ancestor_ts,
-  queuing_time_ns
-FROM scroll_flow_event_queuing_delay
-WHERE trace_id = 2954 OR trace_id = 2956 OR trace_id = 2960
-ORDER BY trace_id, ts;
-""",
-        out=Path('scroll_flow_event_queuing_delay.out'))
-
-  def test_scroll_flow_event_general_validation_2(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query=Path(
-            'scroll_flow_event_queuing_delay_general_validation_test.sql'),
-        out=Path('scroll_flow_event_general_validation.out'))
-
-  def test_scroll_jank_cause_queuing_delay(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_jank_cause_queuing_delay.sql');
-
-SELECT
-  process_name,
-  thread_name,
-  trace_id,
-  jank,
-  dur_overlapping_ns,
-  metric_name
-FROM scroll_jank_cause_queuing_delay
-WHERE trace_id = 2918 OR trace_id = 2926
-ORDER BY trace_id ASC, ts ASC;
-""",
-        out=Path('scroll_jank_cause_queuing_delay.out'))
-
-  def test_scroll_jank_cause_queuing_delay_restricted(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_jank_cause_queuing_delay.sql');
-
-SELECT
-  process_name,
-  thread_name,
-  trace_id,
-  jank,
-  dur_overlapping_ns,
-  restricted_metric_name
-FROM scroll_jank_cause_queuing_delay
-WHERE trace_id = 2918 OR trace_id = 2926
-ORDER BY trace_id ASC, ts ASC;
-""",
-        out=Path('scroll_jank_cause_queuing_delay_restricted.out'))
-
-  def test_scroll_jank_cause_queuing_delay_general_validation(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_jank_cause_queuing_delay.sql');
-
-SELECT
-  COUNT(*) AS total,
-  (
-    SELECT DISTINCT
-      (avg_no_jank_dur_overlapping_ns)
-    FROM scroll_jank_cause_queuing_delay
-    WHERE
-      location = "LatencyInfo.Flow"
-      AND jank
-  ) AS janky_latency_info_non_jank_avg_dur,
-  (
-    SELECT DISTINCT
-      (avg_no_jank_dur_overlapping_ns)
-    FROM scroll_jank_cause_queuing_delay
-    WHERE
-      location = "LatencyInfo.Flow"
-      AND NOT jank
-  ) AS non_janky_latency_info_non_jank_avg_dur
-FROM (
-  SELECT
-    trace_id
-  FROM scroll_jank_cause_queuing_delay
-  GROUP BY trace_id
-);
-""",
-        out=Path('scroll_jank_cause_queuing_delay_general_validation.out'))
-
-  def test_chrome_thread_slice(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_thread_slice.sql');
-
-SELECT
-  EXTRACT_ARG(arg_set_id, 'chrome_latency_info.trace_id') AS trace_id,
-  dur,
-  thread_dur
-FROM chrome_thread_slice
-WHERE
-  name = 'LatencyInfo.Flow'
-  AND EXTRACT_ARG(arg_set_id, 'chrome_latency_info.trace_id') = 2734;
-""",
-        out=Csv("""
-"trace_id","dur","thread_dur"
-2734,25000,25000
-2734,1000,2000
-2734,2000,2000
-2734,258000,171000
-2734,1000,1000
-"""))
-
-  def test_chrome_input_to_browser_intervals(self):
-    return DiffTestBlueprint(
-        trace=Path(
-            '../../data/scrolling_with_blocked_nonblocked_frames.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_input_to_browser_intervals.sql');
-
-SELECT
-  *
-FROM chrome_input_to_browser_intervals
-WHERE window_start_ts >= 60934320005158
-  AND window_start_ts <= 60934338798158;
-""",
-        out=Path('chrome_input_to_browser_intervals.out'))
-
-  def test_chrome_scroll_jank_caused_by_scheduling_test(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/fling_with_input_delay.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_scroll_jank_caused_by_scheduling.sql',
-  'dur_causes_jank_ms',
-/* dur_causes_jank_ms = */ '5');
-
-SELECT
-  full_name,
-  total_duration_ms,
-  total_thread_duration_ms,
-  count,
-  window_start_ts,
-  window_end_ts,
-  scroll_type
-FROM chrome_scroll_jank_caused_by_scheduling;
-""",
-        out=Path('chrome_scroll_jank_caused_by_scheduling_test.out'))
-
-  def test_chrome_tasks_delaying_input_processing_test(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/fling_with_input_delay.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_tasks_delaying_input_processing.sql',
-  'duration_causing_jank_ms',
- /* duration_causing_jank_ms = */ '8');
-
-SELECT
-  full_name,
-  duration_ms,
-  thread_dur_ms
-FROM chrome_tasks_delaying_input_processing;
-""",
-        out=Path('chrome_tasks_delaying_input_processing_test.out'))
-
-  def test_long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_test(
-      self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/long_task_tracking_trace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_long_tasks_delaying_input_processing.sql');
-
-SELECT
-  full_name,
-  duration_ms,
-  slice_id
-FROM chrome_tasks_delaying_input_processing
-ORDER BY slice_id;
-""",
-        out=Path(
-            'long_task_tracking_trace_chrome_long_tasks_delaying_input_processing_test.out'
-        ))
-
-  def test_experimental_reliable_chrome_tasks_delaying_input_processing_test(
-      self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/fling_with_input_delay.pftrace'),
-        query="""
-SELECT RUN_METRIC(
-    'chrome/experimental_reliable_chrome_tasks_delaying_input_processing.sql',
-    'duration_causing_jank_ms', '8');
-
-SELECT
-  full_name,
-  duration_ms,
-  thread_dur_ms
-FROM chrome_tasks_delaying_input_processing;
-""",
-        out=Path(
-            'experimental_reliable_chrome_tasks_delaying_input_processing_test.out'
-        ))
-
-  def test_chrome_scroll_inputs_per_frame_test(self):
-    return DiffTestBlueprint(
-        trace=Path(
-            '../../data/scrolling_with_blocked_nonblocked_frames.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_scroll_inputs_per_frame.sql');
-
-SELECT
-  count_for_frame,
-  ts
-FROM chrome_scroll_inputs_per_frame
-WHERE ts = 60934316798158;
-""",
-        out=Csv("""
-"count_for_frame","ts"
-4,60934316798158
-"""))
-
-  def test_chrome_thread_slice_repeated(self):
-    return DiffTestBlueprint(
-        trace=Path('../track_event/track_event_counters.textproto'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_thread_slice.sql');
-
-SELECT
-  name,
-  ts,
-  dur,
-  thread_dur
-FROM chrome_thread_slice;
-""",
-        out=Csv("""
-"name","ts","dur","thread_dur"
-"event1_on_t1",1000,100,10000
-"event2_on_t1",2000,200,30000
-"event3_on_t1",2000,200,10000
-"event4_on_t1",4000,0,0
-"float_counter_on_t1",4300,0,"[NULL]"
-"float_counter_on_t1",4500,0,"[NULL]"
-"event1_on_t3",4000,100,5000
-"""))
-
-  def test_frame_times_metric(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_rendering_desktop.pftrace'),
-        query=Metric('frame_times'),
-        out=Path('frame_times_metric.out'))
-
-  def test_chrome_dropped_frames_metric(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_rendering_desktop.pftrace'),
-        query=Metric('chrome_dropped_frames'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_dropped_frames]: {
-  dropped_frame: {
-    ts: 166479338462000
-    process_name: "Renderer"
-    pid: 12743
-  }
-  dropped_frame: {
-    ts: 166479355302000
-    process_name: "Renderer"
-    pid: 12743
-  }
-}"""))
-
-  def test_chrome_long_latency_metric(self):
-    return DiffTestBlueprint(
-        trace=Path('../chrome/long_event_latency.textproto'),
-        query="""
-SELECT RUN_METRIC('experimental/chrome_long_latency.sql');
-
-SELECT * FROM long_latency_with_process_info;
-""",
-        out=Csv("""
-"ts","event_type","process_name","process_id"
-200111000,"FirstGestureScrollUpdate,GestureScrollUpdate","Renderer",1001
-200111000,"GestureScrollUpdate","Renderer",1002
-280111001,"GestureScrollUpdate","Renderer",1001
-"""))
-
-  def test_scroll_jank_mojo_simple_watcher(self):
-    return DiffTestBlueprint(
-        trace=Path('scroll_jank_mojo_simple_watcher.py'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_jank_cause_queuing_delay.sql');
-
-SELECT
-  trace_id,
-  jank,
-  dur_overlapping_ns,
-  metric_name
-FROM scroll_jank_cause_queuing_delay
-ORDER BY trace_id ASC, ts ASC;
-""",
-        out=Path('scroll_jank_mojo_simple_watcher.out'))
-
-  def test_scroll_jank_gpu_check(self):
-    return DiffTestBlueprint(
-        trace=Path('scroll_jank_gpu_check.py'),
-        query="""
-SELECT RUN_METRIC('chrome/scroll_jank.sql');
-
-SELECT ts, jank
-FROM scroll_jank
-ORDER BY ts ASC;
-""",
-        out=Csv("""
-"ts","jank"
-15000000,0
-30000000,1
-115000000,0
-"""))
-
-  def test_touch_jank(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_touch_gesture_scroll.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/touch_jank.sql');
-
-SELECT
-  touch_id,
-  trace_id,
-  jank,
-  ts,
-  dur,
-  jank_budget
-FROM touch_jank;
-""",
-        out=Path('touch_jank.out'))
-
-  def test_touch_flow_event(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_touch_gesture_scroll.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/touch_flow_event.sql');
-
-SELECT
-  trace_id,
-  ts,
-  dur,
-  jank,
-  step,
-  ancestor_end,
-  maybe_next_ancestor_ts,
-  next_ts,
-  next_trace_id,
-  next_step
-FROM touch_flow_event
-ORDER BY touch_id, trace_id, ts;
-""",
-        out=Path('touch_flow_event.out'))
-
-  def test_touch_flow_event_queuing_delay(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_touch_gesture_scroll.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/touch_flow_event_queuing_delay.sql');
-
-SELECT
-  trace_id,
-  jank,
-  step,
-  next_step,
-  ancestor_end,
-  maybe_next_ancestor_ts,
-  queuing_time_ns
-FROM touch_flow_event_queuing_delay
-WHERE trace_id = 6915 OR trace_id = 6911 OR trace_id = 6940
-ORDER BY trace_id, ts;
-""",
-        out=Path('touch_flow_event_queuing_delay.out'))
-
-  def test_touch_jank_synth(self):
-    return DiffTestBlueprint(
-        trace=Path('touch_jank.py'),
-        query="""
-SELECT RUN_METRIC('chrome/touch_jank.sql');
-
-SELECT
-  touch_id,
-  trace_id,
-  jank,
-  ts,
-  dur,
-  jank_budget
-FROM touch_jank;
-""",
-        out=Csv("""
-"touch_id","trace_id","jank","ts","dur","jank_budget"
-87654,34577,0,0,10000000,-31333333.350000
-87654,34578,1,16000000,33000000,14666666.650000
-87654,34579,0,55000000,33000000,-8333333.350000
-"""))
-
-  def test_touch_flow_event_synth(self):
-    return DiffTestBlueprint(
-        trace=Path('touch_jank.py'),
-        query="""
-SELECT RUN_METRIC('chrome/touch_flow_event.sql');
-
-SELECT
-  trace_id,
-  ts,
-  dur,
-  jank,
-  step,
-  ancestor_end,
-  maybe_next_ancestor_ts,
-  next_ts,
-  next_trace_id,
-  next_step
-FROM touch_flow_event
-ORDER BY touch_id, trace_id, ts;
-""",
-        out=Path('touch_flow_event_synth.out'))
-
-  def test_touch_flow_event_queuing_delay_synth(self):
-    return DiffTestBlueprint(
-        trace=Path('touch_jank.py'),
-        query="""
-SELECT RUN_METRIC('chrome/touch_flow_event_queuing_delay.sql');
-
-SELECT
-  trace_id,
-  jank,
-  step,
-  next_step,
-  ancestor_end,
-  maybe_next_ancestor_ts,
-  queuing_time_ns
-FROM touch_flow_event_queuing_delay
-ORDER BY trace_id, ts;
-""",
-        out=Path('touch_flow_event_queuing_delay_synth.out'))
-
-  def test_memory_snapshot_general_validation(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_memory_snapshot.pftrace'),
-        query="""
-SELECT
-  (
-    SELECT COUNT(*) FROM memory_snapshot
-  ) AS total_snapshots,
-  (
-    SELECT COUNT(*) FROM process
-  ) AS total_processes,
-  (
-    SELECT COUNT(*) FROM process_memory_snapshot
-  ) AS total_process_snapshots,
-  (
-    SELECT COUNT(*) FROM memory_snapshot_node
-  ) AS total_nodes,
-  (
-    SELECT COUNT(*) FROM memory_snapshot_edge
-  ) AS total_edges,
-  (
-    SELECT COUNT(DISTINCT args.id)
-    FROM args
-    JOIN memory_snapshot_node
-      ON args.arg_set_id = memory_snapshot_node.arg_set_id
-  ) AS total_node_args,
-  (
-    SELECT COUNT(*) FROM profiler_smaps
-    JOIN memory_snapshot ON timestamp = ts
-  ) AS total_smaps;
-""",
-        out=Path('memory_snapshot_general_validation.out'))
-
-  def test_memory_snapshot_os_dump_events(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_memory_snapshot.pftrace'),
-        query="""
-SELECT
-  p.upid,
-  pid,
-  p.name,
-  timestamp,
-  detail_level,
-  pf.value AS private_footprint_kb,
-  prs.value AS peak_resident_set_kb,
-  EXTRACT_ARG(p.arg_set_id, 'is_peak_rss_resettable') AS is_peak_rss_resettable
-FROM process p
-LEFT JOIN memory_snapshot
-LEFT JOIN (
-  SELECT id, upid
-  FROM process_counter_track
-  WHERE name = 'chrome.private_footprint_kb'
-  ) AS pct_pf
-  ON p.upid = pct_pf.upid
-LEFT JOIN counter pf ON timestamp = pf.ts AND pct_pf.id = pf.track_id
-LEFT JOIN (
-  SELECT id, upid
-  FROM process_counter_track
-  WHERE name = 'chrome.peak_resident_set_kb'
-  ) AS pct_prs
-  ON p.upid = pct_prs.upid
-LEFT JOIN counter prs ON timestamp = prs.ts AND pct_prs.id = prs.track_id
-ORDER BY timestamp;
-""",
-        out=Path('memory_snapshot_os_dump_events.out'))
-
-  def test_memory_snapshot_chrome_dump_events(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_memory_snapshot.pftrace'),
-        query="""
-SELECT
-  pms.id AS process_snapshot_id,
-  upid,
-  snapshot_id,
-  timestamp,
-  detail_level
-FROM memory_snapshot ms
-LEFT JOIN process_memory_snapshot pms
-  ON ms.id = pms.snapshot_id;
-""",
-        out=Path('memory_snapshot_chrome_dump_events.out'))
-
-  def test_memory_snapshot_nodes(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_memory_snapshot.pftrace'),
-        query="""
-SELECT
-  id,
-  process_snapshot_id,
-  parent_node_id,
-  path,
-  size,
-  effective_size
-FROM memory_snapshot_node
-LIMIT 20;
-""",
-        out=Path('memory_snapshot_nodes.out'))
-
-  def test_memory_snapshot_edges(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_memory_snapshot.pftrace'),
-        query="""
-SELECT
-  id,
-  source_node_id,
-  target_node_id,
-  importance
-FROM memory_snapshot_edge
-LIMIT 20;
-""",
-        out=Path('memory_snapshot_edges.out'))
-
-  def test_memory_snapshot_node_args(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_memory_snapshot.pftrace'),
-        query="""
-SELECT
-  node.id AS node_id,
-  key,
-  value_type,
-  int_value,
-  string_value
-FROM memory_snapshot_node node
-JOIN args ON node.arg_set_id = args.arg_set_id
-LIMIT 20;
-""",
-        out=Path('memory_snapshot_node_args.out'))
-
-  def test_memory_snapshot_smaps(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_memory_snapshot.pftrace'),
-        query="""
-SELECT
-  process.upid,
-  process.name,
-  smap.ts,
-  path,
-  size_kb,
-  private_dirty_kb,
-  swap_kb,
-  file_name,
-  start_address,
-  module_timestamp,
-  module_debugid,
-  module_debug_path,
-  protection_flags,
-  private_clean_resident_kb,
-  shared_dirty_resident_kb,
-  shared_clean_resident_kb,
-  locked_kb,
-  proportional_resident_kb
-FROM process
-JOIN profiler_smaps smap ON process.upid = smap.upid
-JOIN memory_snapshot ms ON ms.timestamp = smap.ts
-LIMIT 20;
-""",
-        out=Path('memory_snapshot_smaps.out'))
-
-  def test_combined_rail_modes(self):
-    return DiffTestBlueprint(
-        trace=Path('combined_rail_modes.py'),
-        query="""
-SELECT RUN_METRIC('chrome/rail_modes.sql');
-SELECT * FROM combined_overall_rail_slices;
-""",
-        out=Csv("""
-"id","ts","dur","rail_mode"
-1,0,10000,"response"
-2,10000,25000,"animation"
-3,35000,10000,"background"
-"""))
-
-  def test_cpu_time_by_combined_rail_mode(self):
-    return DiffTestBlueprint(
-        trace=Path('cpu_time_by_combined_rail_mode.py'),
-        query="""
-SELECT RUN_METRIC('chrome/cpu_time_by_rail_mode.sql');
-SELECT * FROM cpu_time_by_rail_mode;
-""",
-        out=Csv("""
-"id","ts","dur","rail_mode","cpu_dur"
-1,0,10000,"response",26000
-2,10000,20000,"animation",20000
-3,30000,5000,"background",8000
-4,35000,10000,"animation",21000
-5,45000,10000,"background",1000
-"""))
-
-  def test_actual_power_by_combined_rail_mode(self):
-    return DiffTestBlueprint(
-        trace=Path('actual_power_by_combined_rail_mode.py'),
-        query="""
-SELECT RUN_METRIC('chrome/actual_power_by_rail_mode.sql');
-SELECT * FROM real_power_by_rail_mode;
-""",
-        out=Csv("""
-"id","ts","dur","rail_mode","subsystem","joules","drain_w"
-1,0,10000000,"response","cellular",0.000000,0.000000
-1,0,10000000,"response","cpu_little",0.000140,0.014000
-2,10000000,20000000,"animation","cellular",0.000350,0.017500
-2,10000000,20000000,"animation","cpu_little",0.000140,0.007000
-3,30000000,5000000,"background","cellular",0.000018,0.003500
-3,30000000,5000000,"background","cpu_little",0.000007,0.001400
-4,35000000,10000000,"animation","cellular",0.000021,0.002100
-4,35000000,10000000,"animation","cpu_little",0.000070,0.007000
-5,45000000,10000000,"background","cellular",0.000003,0.000350
-5,45000000,10000000,"background","cpu_little",0.000070,0.007000
-"""))
-
-  def test_estimated_power_by_combined_rail_mode(self):
-    return DiffTestBlueprint(
-        trace=Path('estimated_power_by_combined_rail_mode.py'),
-        query="""
-SELECT RUN_METRIC('chrome/estimated_power_by_rail_mode.sql');
-SELECT * FROM power_by_rail_mode;
-""",
-        out=Csv("""
-"id","ts","dur","rail_mode","mas","ma"
-1,0,10000000,"response",0.554275,55.427500
-2,10000000,20000000,"animation",0.284850,14.242500
-3,30000000,5000000,"background",0.076233,15.246667
-4,35000000,10000000,"animation",0.536850,53.685000
-5,45000000,10000000,"background",0.071580,7.158000
-"""))
-
-  def test_modified_rail_modes(self):
-    return DiffTestBlueprint(
-        trace=Path('modified_rail_modes.py'),
-        query="""
-SELECT RUN_METRIC('chrome/rail_modes.sql');
-SELECT * FROM modified_rail_slices;
-""",
-        out=Csv("""
-"id","ts","dur","mode"
-2,0,1000000000,"response"
-3,1000000000,1950000000,"foreground_idle"
-4,2950000000,333333324,"animation"
-5,3283333324,216666676,"foreground_idle"
-6,3500000000,1000000000,"background"
-"""))
-
-  def test_modified_rail_modes_no_vsyncs(self):
-    return DiffTestBlueprint(
-        trace=Path('modified_rail_modes_no_vsyncs.py'),
-        query="""
-SELECT RUN_METRIC('chrome/rail_modes.sql');
-SELECT * FROM modified_rail_slices;
-""",
-        out=Csv("""
-"id","ts","dur","mode"
-2,0,1000000000,"response"
-3,1000000000,2500000000,"foreground_idle"
-4,3500000000,1000000000,"background"
-"""))
-
-  def test_modified_rail_modes_with_input(self):
-    return DiffTestBlueprint(
-        trace=Path('modified_rail_modes_with_input.py'),
-        query="""
-SELECT RUN_METRIC('chrome/rail_modes.sql');
-SELECT * FROM modified_rail_slices;
-""",
-        out=Csv("""
-"id","ts","dur","mode"
-2,0,1000000000,"response"
-3,1000000000,1950000000,"foreground_idle"
-4,2950000000,50000000,"animation"
-5,3000000000,66666674,"response"
-6,3066666674,216666650,"animation"
-7,3283333324,216666676,"foreground_idle"
-8,3500000000,1000000000,"background"
-"""))
-
-  def test_modified_rail_modes_long(self):
-    return DiffTestBlueprint(
-        trace=Path('modified_rail_modes_long.py'),
-        query="""
-SELECT RUN_METRIC('chrome/rail_modes.sql');
-SELECT * FROM modified_rail_slices;
-""",
-        out=Csv("""
-"id","ts","dur","mode"
-2,0,1000000000,"response"
-3,1000000000,1,"background"
-"""))
-
-  def test_modified_rail_modes_extra_long(self):
-    return DiffTestBlueprint(
-        trace=Path('modified_rail_modes_extra_long.py'),
-        query="""
-SELECT RUN_METRIC('chrome/rail_modes.sql');
-SELECT * FROM modified_rail_slices;
-""",
-        out=Csv("""
-"id","ts","dur","mode"
-"""))
-
-  def test_chrome_processes(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_processes.sql');
-SELECT pid, name, process_type FROM chrome_process;
-""",
-        out=Csv("""
-"pid","name","process_type"
-18250,"Renderer","Renderer"
-17547,"Browser","Browser"
-18277,"GPU Process","Gpu"
-17578,"Browser","Browser"
-"""))
-
-  def test_chrome_processes_android_systrace(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_android_systrace.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_processes.sql');
-SELECT pid, name, process_type FROM chrome_process;
-""",
-        out=Path('chrome_processes_android_systrace.out'))
-
-  def test_chrome_threads(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_processes.sql');
-SELECT tid, name, is_main_thread, canonical_name
-FROM chrome_thread
-ORDER BY tid, name;
-""",
-        out=Path('chrome_threads.out'))
-
-  def test_chrome_threads_android_systrace(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_android_systrace.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_processes.sql');
-SELECT tid, name, is_main_thread, canonical_name
-FROM chrome_thread
-ORDER BY tid, name;
-""",
-        out=Path('chrome_threads_android_systrace.out'))
-
-  def test_chrome_processes_type(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT pid, name, string_value AS chrome_process_type
-FROM
-  process
-JOIN
-  (SELECT * FROM args WHERE key = "chrome.process_type") chrome_process_args
-  ON
-    process.arg_set_id = chrome_process_args.arg_set_id
-ORDER BY pid;
-""",
-        out=Csv("""
-"pid","name","chrome_process_type"
-17547,"Browser","Browser"
-17578,"Browser","Browser"
-18250,"Renderer","Renderer"
-18277,"GPU Process","Gpu"
-"""))
-
-  def test_chrome_processes_type_android_systrace(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_android_systrace.pftrace'),
-        query="""
-SELECT pid, name, string_value AS chrome_process_type
-FROM
-  process
-JOIN
-  (SELECT * FROM args WHERE key = "chrome.process_type") chrome_process_args
-  ON
-    process.arg_set_id = chrome_process_args.arg_set_id
-ORDER BY pid;
-""",
-        out=Path('chrome_processes_type_android_systrace.out'))
-
-  def test_track_with_chrome_process(self):
-    return DiffTestBlueprint(
-        trace=Path('track_with_chrome_process.textproto'),
-        query="""
-SELECT pid, name, string_value AS chrome_process_type
-FROM
-  process
-JOIN
-  (SELECT * FROM args WHERE key = "chrome.process_type") chrome_process_args
-  ON
-    process.arg_set_id = chrome_process_args.arg_set_id
-ORDER BY pid;
-""",
-        out=Csv("""
-"pid","name","chrome_process_type"
-5,"p5","[NULL]"
-"""))
-
-  def test_chrome_histogram_hashes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_histogram_hashes.textproto'),
-        query=Metric('chrome_histogram_hashes'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_histogram_hashes]: {
-  hash: 10
-  hash: 20
-}
-"""))
-
-  def test_chrome_user_event_hashes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_user_event_hashes.textproto'),
-        query=Metric('chrome_user_event_hashes'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_user_event_hashes]: {
-  action_hash: 10
-  action_hash: 20
-}
-
-"""))
-
-  def test_chrome_performance_mark_hashes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_performance_mark_hashes.textproto'),
-        query=Metric('chrome_performance_mark_hashes'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_performance_mark_hashes]: {
-  site_hash: 10
-  site_hash: 20
-  mark_hash: 100
-  mark_hash: 200
-}
-"""))
-
-  def test_chrome_reliable_range(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-"start","reason","debug_limiting_upid","debug_limiting_utid"
-12,"First slice for utid=2","[NULL]",2
-"""))
-
-  def test_chrome_reliable_range_cropping(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_cropping.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-"start","reason","debug_limiting_upid","debug_limiting_utid"
-10000,"Range of interest packet","[NULL]",2
-"""))
-
-  def test_chrome_reliable_range_missing_processes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_missing_processes.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-"start","reason","debug_limiting_upid","debug_limiting_utid"
-1011,"Missing process data for upid=2",2,1
-"""))
-
-  def test_chrome_reliable_range_missing_browser_main(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_missing_browser_main.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-"start","reason","debug_limiting_upid","debug_limiting_utid"
-1011,"Missing main thread for upid=1",1,1
-"""))
-
-  def test_chrome_reliable_range_missing_renderer_main(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_missing_renderer_main.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-"start","reason","debug_limiting_upid","debug_limiting_utid"
-1011,"Missing main thread for upid=1",1,1
-"""))
-
-  def test_chrome_reliable_range_non_chrome_process(self):
-    return DiffTestBlueprint(
-        # We need a trace with a large number of non-chrome slices, so that the
-        # reliable range is affected by their filtering.
-        trace=Path('../../data/example_android_trace_30s.pb'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-  "start","reason","debug_limiting_upid","debug_limiting_utid"
-  0,"[NULL]","[NULL]","[NULL]"
-  """))
-
-  def test_chrome_slice_names(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_slice_names.textproto'),
-        query=Metric('chrome_slice_names'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_slice_names]: {
-  chrome_version_code: 123
-  slice_name: "Looper.Dispatch: class1"
-  slice_name: "name2"
-}
-"""))
-
-  def test_chrome_tasks(self):
-    return DiffTestBlueprint(
-        trace=Path(
-            '../../data/chrome_page_load_all_categories_not_extended.pftrace.gz'
-        ),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_tasks.sql');
-
-SELECT full_name, task_type, count() AS count
-FROM chrome_tasks
-GROUP BY full_name, task_type
-ORDER BY count DESC
-LIMIT 50;
-""",
-        out=Path('chrome_tasks.out'))
-
-  def test_top_level_java_choreographer_slices_top_level_java_chrome_tasks_test(
-      self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/top_level_java_choreographer_slices'),
-        query="""
-SELECT RUN_METRIC(
-  'chrome/chrome_tasks_template.sql',
-  'slice_table_name', 'slice',
-  'function_prefix', ''
-);
-
-SELECT
-  full_name,
-  task_type
-FROM chrome_tasks
-WHERE category = "toplevel,Java"
-AND ts < 263904000000000
-GROUP BY full_name, task_type;
-""",
-        out=Path(
-            'top_level_java_choreographer_slices_top_level_java_chrome_tasks_test.out'
-        ))
-
-  def test_chrome_stack_samples_for_task_test(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_stack_traces_symbolized_trace.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_stack_samples_for_task.sql',
-    'target_duration_ms', '0.000001',
-    'thread_name', '"CrBrowserMain"',
-    'task_name', '"sendTouchEvent"');
-
-SELECT
-  sample.description,
-  sample.ts,
-  sample.depth
-FROM chrome_stack_samples_for_task sample
-JOIN (
-    SELECT
-      ts,
-      dur
-    FROM slice
-    WHERE ts = 696373965001470
-) test_slice
-ON sample.ts >= test_slice.ts
-  AND sample.ts <= test_slice.ts + test_slice.dur
-ORDER BY sample.ts, sample.depth;
-""",
-        out=Path('chrome_stack_samples_for_task_test.out'))
-
-  def test_unsymbolized_args(self):
-    return DiffTestBlueprint(
-        trace=Path('unsymbolized_args.textproto'),
-        query=Metric('chrome_unsymbolized_args'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_unsymbolized_args]: {
-  args {
-     module: "/liblib.so"
-     build_id: "6275696c642d6964"
-     address: 123
-     google_lookup_id: "6275696c642d6964"
-   }
-   args {
-     module: "/libmonochrome_64.so"
-     build_id: "7f0715c286f8b16c10e4ad349cda3b9b56c7a773"
-     address: 234
-     google_lookup_id: "c215077ff8866cb110e4ad349cda3b9b0"
-   }
-}"""))
-
-  def test_async_trace_1_count_slices(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/async-trace-1.json'),
-        query="""
-SELECT COUNT(1) FROM slice;
-""",
-        out=Csv("""
-"COUNT(1)"
-16
-"""))
-
-  def test_async_trace_2_count_slices(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/async-trace-2.json'),
-        query="""
-SELECT COUNT(1) FROM slice;
-""",
-        out=Csv("""
-"COUNT(1)"
-35
-"""))
-
-  def test_chrome_args_class_names(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_args_class_names.textproto'),
-        query=Metric('chrome_args_class_names'),
-        out=TextProto(r"""
-
-[perfetto.protos.chrome_args_class_names] {
-  class_names_per_version {
-    class_name: "abc"
-    class_name: "def"
-    class_name: "ghi"
-    class_name: "jkl"
-  }
-}
-"""))
-
-  def test_chrome_log_message(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_log_message.textproto'),
-        query="""
-SELECT utid, tag, msg FROM android_logs;
-""",
-        out=Csv("""
-"utid","tag","msg"
-1,"foo.cc:123","log message"
-"""))
-
-  def test_chrome_log_message_args(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_log_message.textproto'),
-        query=Path('chrome_log_message_args_test.sql'),
-        out=Csv("""
-"log_message","function_name","file_name","line_number"
-"log message","func","foo.cc",123
-"""))
-
-  def test_chrome_missing_processes_default_trace(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT upid, pid, reliable_from
-FROM
-  experimental_missing_chrome_processes
-JOIN
-  process
-  USING(upid)
-ORDER BY upid;
-""",
-        out=Csv("""
-"upid","pid","reliable_from"
-"""))
-
-  def test_chrome_missing_processes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_missing_processes.textproto'),
-        query="""
-SELECT upid, pid, reliable_from
-FROM
-  experimental_missing_chrome_processes
-JOIN
-  process
-  USING(upid)
-ORDER BY upid;
-""",
-        out=Csv("""
-"upid","pid","reliable_from"
-2,100,1000000000
-3,1000,"[NULL]"
-"""))
-
-  def test_chrome_missing_processes_args(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_missing_processes.textproto'),
-        query="""
-SELECT arg_set_id, key, int_value
-FROM
-  slice
-JOIN
-  args
-  USING(arg_set_id)
-ORDER BY arg_set_id, key;
-""",
-        out=Csv("""
-"arg_set_id","key","int_value"
-2,"chrome_active_processes.pid[0]",10
-2,"chrome_active_processes.pid[1]",100
-2,"chrome_active_processes.pid[2]",1000
-"""))
-
-  def test_chrome_missing_processes_2(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_missing_processes_extension.textproto'),
-        query="""
-SELECT upid, pid, reliable_from
-FROM
-  experimental_missing_chrome_processes
-JOIN
-  process
-  USING(upid)
-ORDER BY upid;
-""",
-        out=Csv("""
-"upid","pid","reliable_from"
-2,100,1000000000
-3,1000,"[NULL]"
-"""))
-
-  def test_chrome_missing_processes_extension_args(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_missing_processes_extension.textproto'),
-        query="""
-SELECT arg_set_id, key, int_value
-FROM
-  slice
-JOIN
-  args
-  USING(arg_set_id)
-ORDER BY arg_set_id, key;
-""",
-        out=Csv("""
-"arg_set_id","key","int_value"
-2,"active_processes.pid[0]",10
-2,"active_processes.pid[1]",100
-2,"active_processes.pid[2]",1000
-"""))
-
-  def test_chrome_custom_navigation_tasks(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_custom_navigation_trace.gz'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_tasks.sql');
-
-SELECT full_name, task_type, count() AS count
-FROM chrome_tasks
-WHERE full_name GLOB 'FrameHost::BeginNavigation*'
-  OR full_name GLOB 'FrameHost::DidCommitProvisionalLoad*'
-  OR full_name GLOB 'FrameHost::DidCommitSameDocumentNavigation*'
-  OR full_name GLOB 'FrameHost::DidStopLoading*'
-GROUP BY full_name, task_type
-ORDER BY count DESC
-LIMIT 50;
-""",
-        out=Csv("""
-"full_name","task_type","count"
-"FrameHost::BeginNavigation (SUBFRAME)","navigation_task",5
-"FrameHost::DidStopLoading (SUBFRAME)","navigation_task",3
-"FrameHost::BeginNavigation (PRIMARY_MAIN_FRAME)","navigation_task",1
-"FrameHost::DidCommitProvisionalLoad (SUBFRAME)","navigation_task",1
-"""))
-
-  def test_proto_content(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/chrome_scroll_without_vsync.pftrace'),
-        query="""
-SELECT path, SUM(total_size) as total_size
-FROM experimental_proto_content as content 
-JOIN experimental_proto_path as frame ON content.path_id = frame.id
-GROUP BY path
-ORDER BY total_size DESC, path
-LIMIT 10;
-""",
-        out=Path('proto_content.out'))
-
-  def test_chrome_scroll_jank_v2(self):
-    return DiffTestBlueprint(
-        trace=Path('../../data/event_latency_with_args.perfetto-trace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_scroll_jank_v2.sql');
-
-SELECT
-  scroll_processing_ms,
-  scroll_jank_processing_ms,
-  scroll_jank_percentage
-FROM chrome_scroll_jank_v2_output;
-""",
-        out=Path('chrome_scroll_jank_v2.out'))
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.py b/test/trace_processor/diff_tests/chrome/tests.py
index ec4181e..c6187aa 100644
--- a/test/trace_processor/diff_tests/chrome/tests.py
+++ b/test/trace_processor/diff_tests/chrome/tests.py
@@ -253,11 +253,11 @@
         query="""
         SELECT RUN_METRIC('chrome/chrome_tasks.sql');
 
-        SELECT full_name, task_type, count() AS count
+        SELECT full_name as name, task_type, count() AS count
         FROM chrome_tasks
         GROUP BY full_name, task_type
-        ORDER BY count DESC
-        LIMIT 50;
+        HAVING count >= 5
+        ORDER BY count DESC, name;
         """,
         out=Path('chrome_tasks.out'))
 
@@ -446,6 +446,45 @@
         "FrameHost::DidCommitProvisionalLoad (SUBFRAME)","navigation_task",1
         """))
 
+  # Chrome custom navigation event names
+  def test_chrome_histograms(self):
+    return DiffTestBlueprint(
+        trace=DataPath('chrome_5672_histograms.pftrace.gz'),
+        query="""
+        SELECT IMPORT('chrome.histograms');
+
+        SELECT 
+          name,
+          count() as count 
+        FROM chrome_histograms
+        GROUP BY name
+        ORDER BY count DESC, name
+        LIMIT 20;
+        """,
+        out=Csv("""
+        "name","count"
+        "Net.QuicSession.AsyncRead",19207
+        "Net.QuicSession.NumQueuedPacketsBeforeWrite",19193
+        "RendererScheduler.QueueingDuration.NormalPriority",9110
+        "Net.OnTransferSizeUpdated.Experimental.OverridenBy",8525
+        "Compositing.Renderer.AnimationUpdateOnMissingPropertyNode",3489
+        "Net.QuicConnection.WritePacketStatus",3099
+        "Net.QuicSession.PacketWriteTime.Synchronous",3082
+        "Net.QuicSession.SendPacketSize.ForwardSecure",3012
+        "Net.URLLoaderThrottleExecutionTime.WillStartRequest",1789
+        "Net.URLLoaderThrottleExecutionTime.BeforeWillProcessResponse",1773
+        "Net.URLLoaderThrottleExecutionTime.WillProcessResponse",1773
+        "UMA.StackProfiler.SampleInOrder",1534
+        "GPU.SharedImage.ContentConsumed",1037
+        "Gpu.Rasterization.Raster.MSAASampleCountLog2",825
+        "Scheduling.Renderer.DeadlineMode",637
+        "Blink.CullRect.UpdateTime",622
+        "Scheduling.Renderer.BeginImplFrameLatency2",591
+        "Net.QuicSession.CoalesceStreamFrameStatus",551
+        "API.StorageAccess.AllowedRequests2",541
+        "Net.HttpResponseCode",541
+        """))
+
   # Trace proto content
   def test_proto_content(self):
     return DiffTestBlueprint(
@@ -453,6 +492,12 @@
         query=Path('proto_content_test.sql'),
         out=Path('proto_content.out'))
 
+  def test_speedometer(self):
+    return DiffTestBlueprint(
+        trace=DataPath('speedometer.perfetto_trace.gz'),
+        query=Path('chrome_speedometer_test.sql'),
+        out=Path('chrome_speedometer.out'))
+
   # TODO(mayzner): Uncomment when it works
   # def test_proto_content_path(self):
   #   return DiffTestBlueprint(
diff --git a/test/trace_processor/diff_tests/chrome/tests_general.py b/test/trace_processor/diff_tests/chrome/tests_general.py
deleted file mode 100644
index 99938b8..0000000
--- a/test/trace_processor/diff_tests/chrome/tests_general.py
+++ /dev/null
@@ -1,212 +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 ChromeGeneral(DiffTestModule):
-
-  def test_chrome_histogram_hashes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_histogram_hashes.textproto'),
-        query=Metric('chrome_histogram_hashes'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_histogram_hashes]: {
-  hash: 10
-  hash: 20
-}
-"""))
-
-  def test_chrome_user_event_hashes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_user_event_hashes.textproto'),
-        query=Metric('chrome_user_event_hashes'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_user_event_hashes]: {
-  action_hash: 10
-  action_hash: 20
-}
-
-"""))
-
-  def test_chrome_performance_mark_hashes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_performance_mark_hashes.textproto'),
-        query=Metric('chrome_performance_mark_hashes'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_performance_mark_hashes]: {
-  site_hash: 10
-  site_hash: 20
-  mark_hash: 100
-  mark_hash: 200
-}
-"""))
-
-  def test_chrome_reliable_range(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-"start","reason","debug_limiting_upid","debug_limiting_utid"
-12,"First slice for utid=2","[NULL]",2
-"""))
-
-  def test_chrome_reliable_range_cropping(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_cropping.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-"start","reason","debug_limiting_upid","debug_limiting_utid"
-10000,"Range of interest packet","[NULL]",2
-"""))
-
-  def test_chrome_reliable_range_missing_processes(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_reliable_range_missing_processes.textproto'),
-        query=Path('chrome_reliable_range_test.sql'),
-        out=Csv("""
-"start","reason","debug_limiting_upid","debug_limiting_utid"
-1011,"Missing process data for upid=2",2,1
-"""))
-
-  def test_chrome_slice_names(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_slice_names.textproto'),
-        query=Metric('chrome_slice_names'),
-        out=TextProto(r"""
-[perfetto.protos.chrome_slice_names]: {
-  chrome_version_code: 123
-  slice_name: "Looper.Dispatch: class1"
-  slice_name: "name2"
-}
-"""))
-
-  def test_chrome_tasks(self):
-    return DiffTestBlueprint(
-        trace=DataPath(
-            'chrome_page_load_all_categories_not_extended.pftrace.gz'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_tasks.sql');
-
-SELECT full_name, task_type, count() AS count
-FROM chrome_tasks
-GROUP BY full_name, task_type
-ORDER BY count DESC
-LIMIT 50;
-""",
-        out=Path('chrome_tasks.out'))
-
-  def test_top_level_java_choreographer_slices_top_level_java_chrome_tasks(
-      self):
-    return DiffTestBlueprint(
-        trace=DataPath('top_level_java_choreographer_slices'),
-        query="""
-SELECT RUN_METRIC(
-  'chrome/chrome_tasks_template.sql',
-  'slice_table_name', 'slice',
-  'function_prefix', ''
-);
-
-SELECT
-  full_name,
-  task_type
-FROM chrome_tasks
-WHERE category = "toplevel,Java"
-AND ts < 263904000000000
-GROUP BY full_name, task_type;
-""",
-        out=Path(
-            'top_level_java_choreographer_slices_top_level_java_chrome_tasks_test.out'
-        ))
-
-  def test_chrome_stack_samples_for_task(self):
-    return DiffTestBlueprint(
-        trace=DataPath('chrome_stack_traces_symbolized_trace.pftrace'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_stack_samples_for_task.sql',
-    'target_duration_ms', '0.000001',
-    'thread_name', '"CrBrowserMain"',
-    'task_name', '"sendTouchEvent"');
-
-SELECT
-  sample.description,
-  sample.ts,
-  sample.depth
-FROM chrome_stack_samples_for_task sample
-JOIN (
-    SELECT
-      ts,
-      dur
-    FROM slice
-    WHERE ts = 696373965001470
-) test_slice
-ON sample.ts >= test_slice.ts
-  AND sample.ts <= test_slice.ts + test_slice.dur
-ORDER BY sample.ts, sample.depth;
-""",
-        out=Path('chrome_stack_samples_for_task_test.out'))
-
-  def test_chrome_log_message(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_log_message.textproto'),
-        query="""
-SELECT utid, tag, msg FROM android_logs;
-""",
-        out=Csv("""
-"utid","tag","msg"
-1,"foo.cc:123","log message"
-"""))
-
-  def test_chrome_log_message_args(self):
-    return DiffTestBlueprint(
-        trace=Path('chrome_log_message.textproto'),
-        query=Path('chrome_log_message_args_test.sql'),
-        out=Csv("""
-"log_message","function_name","file_name","line_number"
-"log message","func","foo.cc",123
-"""))
-
-  def test_chrome_custom_navigation_tasks(self):
-    return DiffTestBlueprint(
-        trace=DataPath('chrome_custom_navigation_trace.gz'),
-        query="""
-SELECT RUN_METRIC('chrome/chrome_tasks.sql');
-
-SELECT full_name, task_type, count() AS count
-FROM chrome_tasks
-WHERE full_name GLOB 'FrameHost::BeginNavigation*'
-  OR full_name GLOB 'FrameHost::DidCommitProvisionalLoad*'
-  OR full_name GLOB 'FrameHost::DidCommitSameDocumentNavigation*'
-  OR full_name GLOB 'FrameHost::DidStopLoading*'
-GROUP BY full_name, task_type
-ORDER BY count DESC
-LIMIT 50;
-""",
-        out=Csv("""
-"full_name","task_type","count"
-"FrameHost::BeginNavigation (SUBFRAME)","navigation_task",5
-"FrameHost::DidStopLoading (SUBFRAME)","navigation_task",3
-"FrameHost::BeginNavigation (PRIMARY_MAIN_FRAME)","navigation_task",1
-"FrameHost::DidCommitProvisionalLoad (SUBFRAME)","navigation_task",1
-"""))
-
-  def test_proto_content(self):
-    return DiffTestBlueprint(
-        trace=DataPath('chrome_scroll_without_vsync.pftrace'),
-        query=Path('proto_content_test.sql'),
-        out=Path('proto_content.out'))
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..75668e9 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,54 @@
         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
+        """))
+
+  def test_chrome_scroll_jank_v2(self):
+    return DiffTestBlueprint(
+        trace=DataPath('event_latency_with_args.perfetto-trace'),
+        query=Metric('chrome_scroll_jank_v2'),
+        out=TextProto(r"""
+        [perfetto.protos.chrome_scroll_jank_v2] {
+          scroll_processing_ms: 12374.56
+          scroll_jank_processing_ms: 154.217
+          scroll_jank_percentage: 1.2462422906349802
+          num_scroll_janks: 4
+          scroll_jank_causes_and_durations {
+            cause: "SubmitCompositorFrameToPresentationCompositorFrame"
+            duration_ms: 39.44
+          }
+          scroll_jank_causes_and_durations {
+            cause: "SubmitCompositorFrameToPresentationCompositorFrame"
+            duration_ms: 35.485
+          }
+          scroll_jank_causes_and_durations {
+            cause: "SubmitCompositorFrameToPresentationCompositorFrame"
+            duration_ms: 43.838
+          }
+          scroll_jank_causes_and_durations {
+            cause: "SubmitCompositorFrameToPresentationCompositorFrame"
+            duration_ms: 35.454
+          }
+        }
+        """))
diff --git a/test/trace_processor/diff_tests/cros/tests_general.py b/test/trace_processor/diff_tests/cros/tests_general.py
deleted file mode 100644
index 5f012db..0000000
--- a/test/trace_processor/diff_tests/cros/tests_general.py
+++ /dev/null
@@ -1,40 +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 CrosGeneral(DiffTestModule):
-
-  def test_cros_ec_sensorhub_data(self):
-    return DiffTestBlueprint(
-        trace=Path('cros_ec_sensorhub_data.textproto'),
-        query="""
-SELECT
-  t.name,
-  c.ts,
-  c.value,
-  EXTRACT_ARG(c.arg_set_id, 'ec_num') AS ec_num,
-  EXTRACT_ARG(c.arg_set_id, 'ec_delta') AS ec_delta,
-  EXTRACT_ARG(c.arg_set_id, 'sample_ts') AS sample_ts
-FROM counter c
-JOIN track t
-  ON c.track_id = t.id
-WHERE t.name = 'cros_ec.cros_ec_sensorhub_data.0';
-""",
-        out=Path('cros_ec_sensorhub_data.out'))
diff --git a/test/trace_processor/diff_tests/functions/tests.py b/test/trace_processor/diff_tests/functions/tests.py
index 7f952ed..e5dc15c 100644
--- a/test/trace_processor/diff_tests/functions/tests.py
+++ b/test/trace_processor/diff_tests/functions/tests.py
@@ -520,3 +520,21 @@
         4,3
         5,3
         """))
+
+  def test_math_functions(self):
+    return DiffTestBlueprint(
+        trace=TextProto(""),
+        query="""
+        SELECT
+          CAST(EXP(1) * 1000 AS INTEGER) AS a,
+          CAST(LN(1) * 1000 AS INTEGER) AS b,
+          CAST(LN(EXP(1)) * 1000 AS INTEGER) AS c,
+          EXP("asd") AS d,
+          EXP(NULL) AS e,
+          LN("as") AS f,
+          LN(NULL) AS g
+        """,
+        out=Csv("""
+        "a","b","c","d","e","f","g"
+        2718,0,1000,"[NULL]","[NULL]","[NULL]","[NULL]"
+        """))
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 5784a98..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
@@ -196,6 +197,7 @@
         apk_version_code: 1
         debuggable: false
       }
+      pid: 1000
     }
     ts: 0
     dur: 901000010
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..9a78653 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/span_join/tests.py b/test/trace_processor/diff_tests/span_join/tests.py
deleted file mode 100644
index e675aff..0000000
--- a/test/trace_processor/diff_tests/span_join/tests.py
+++ /dev/null
@@ -1,411 +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 DiffTestModule_SpanJoin(DiffTestModule):
-
-  def test_span_outer_join(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query=Path('span_outer_join_test.sql'),
-        out=Path('span_outer_join.out'))
-
-  def test_span_outer_join_empty(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-INSERT INTO t1(ts, dur, part)
-VALUES (500, 100, 10);
-
-
-CREATE VIRTUAL TABLE sp USING span_outer_join(t1 PARTITIONED part,
-                                              t2 PARTITIONED part);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur","part"
-500,100,10
-"""))
-
-  def test_span_outer_join_unpartitioned_empty(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-
-CREATE VIRTUAL TABLE sp USING span_outer_join(t1, t2);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur"
-"""))
-
-  def test_span_outer_join_unpartitioned_left_empty(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-INSERT INTO t2(ts, dur)
-VALUES
-(100, 400),
-(500, 50),
-(600, 100);
-
-CREATE VIRTUAL TABLE sp USING span_outer_join(t1, t2);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur"
-100,400
-500,50
-600,100
-"""))
-
-  def test_span_outer_join_unpartitioned_right_empty(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-INSERT INTO t1(ts, dur)
-VALUES
-(100, 400),
-(500, 50),
-(600, 100);
-
-CREATE VIRTUAL TABLE sp USING span_outer_join(t1, t2);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur"
-100,400
-500,50
-600,100
-"""))
-
-  def test_span_outer_join_mixed(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query=Path('span_outer_join_mixed_test.sql'),
-        out=Path('span_outer_join_mixed.out'))
-
-  def test_span_outer_join_mixed_empty(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-
-CREATE VIRTUAL TABLE sp USING span_outer_join(t1 PARTITIONED part, t2);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur","part"
-"""))
-
-  def test_span_outer_join_mixed_left_empty(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-INSERT INTO t2(ts, dur)
-VALUES
-(100, 400),
-(500, 50),
-(600, 100);
-
-CREATE VIRTUAL TABLE sp USING span_outer_join(t1 PARTITIONED part, t2);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur","part"
-"""))
-
-  def test_span_outer_join_mixed_left_empty_rev(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-INSERT INTO t1(ts, dur, part)
-VALUES
-(100, 400, 0),
-(100, 50, 1),
-(600, 100, 1);
-
-CREATE VIRTUAL TABLE sp USING span_outer_join(t2, t1 PARTITIONED part);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur","part"
-100,400,0
-100,50,1
-600,100,1
-"""))
-
-  def test_span_outer_join_mixed_right_empty(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  b BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-INSERT INTO t1(ts, dur, part)
-VALUES
-(100, 400, 0),
-(100, 50, 1),
-(600, 100, 1);
-
-CREATE VIRTUAL TABLE sp USING span_outer_join(t1 PARTITIONED part, t2);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur","part","b"
-100,400,0,"[NULL]"
-100,50,1,"[NULL]"
-600,100,1,"[NULL]"
-"""))
-
-  def test_span_outer_join_mixed_right_empty_rev(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  b BIGINT,
-  PRIMARY KEY (ts)
-) WITHOUT ROWID;
-
-INSERT INTO t2(ts, dur)
-VALUES
-(100, 400),
-(500, 50),
-(600, 100);
-
-CREATE VIRTUAL TABLE sp USING span_outer_join(t2, t1 PARTITIONED part);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur","part","b"
-"""))
-
-  def test_span_outer_join_mixed_2(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query=Path('span_outer_join_mixed_test.sql'),
-        out=Path('span_outer_join_mixed.out'))
-
-  def test_span_left_join(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query=Path('span_left_join_test.sql'),
-        out=Path('span_left_join.out'))
-
-  def test_span_left_join_unpartitioned(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query=Path('span_left_join_unpartitioned_test.sql'),
-        out=Path('span_left_join_unpartitioned.out'))
-
-  def test_span_left_join_left_unpartitioned(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query=Path('span_left_join_left_unpartitioned_test.sql'),
-        out=Path('span_left_join_left_unpartitioned.out'))
-
-  def test_span_left_join_left_partitioned(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query=Path('span_left_join_left_partitioned_test.sql'),
-        out=Path('span_left_join_left_partitioned.out'))
-
-  def test_span_left_join_empty_right(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-INSERT INTO t1(ts, dur, part)
-VALUES
-(500, 500, 100);
-
-CREATE VIRTUAL TABLE sp USING span_left_join(t1 PARTITIONED part,
-                                             t2 PARTITIONED part);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur","part"
-500,500,100
-"""))
-
-  def test_span_left_join_unordered_android_sched_and_ps(self):
-    return DiffTestBlueprint(
-        trace=Path('../common/synth_1.py'),
-        query="""
-CREATE TABLE t1(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-CREATE TABLE t2(
-  ts BIGINT,
-  dur BIGINT,
-  part BIGINT,
-  PRIMARY KEY (part, ts)
-) WITHOUT ROWID;
-
-INSERT INTO t1(ts, dur, part)
-VALUES (500, 100, 10);
-
-INSERT INTO t2(ts, dur, part)
-VALUES (500, 100, 5);
-
-CREATE VIRTUAL TABLE sp USING span_left_join(t1 PARTITIONED part,
-                                             t2 PARTITIONED part);
-
-SELECT * FROM sp;
-""",
-        out=Csv("""
-"ts","dur","part"
-500,100,10
-"""))
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 8336103..ea96296 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_attribution.out
+++ b/test/trace_processor/diff_tests/startup/android_startup_attribution.out
@@ -49,6 +49,10 @@
         dur_ns: 499999845
         dur_ms: 499.999845
       }
+      time_dlopen_thread_main {
+        dur_ns: 2
+        dur_ms: 2e-06
+      }
     }
     activity_hosting_process_count: 1
     process {
@@ -64,6 +68,7 @@
         apk_version_code: 123
         debuggable: false
       }
+      pid: 3
     }
     event_timestamps {
       intent_received: 100
@@ -110,6 +115,8 @@
       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*"
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 0d7e784..59301e0 100644
--- a/test/trace_processor/diff_tests/startup/android_startup_attribution.py
+++ b/test/trace_processor/diff_tests/startup/android_startup_attribution.py
@@ -85,6 +85,15 @@
     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 80f6f81..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
@@ -64,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 52dd550..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"
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 254759a..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"
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 53d53b9..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
diff --git a/test/trace_processor/diff_tests/tables/tests_sched.py b/test/trace_processor/diff_tests/tables/tests_sched.py
index 1f1ee49..3ef8d14 100644
--- a/test/trace_processor/diff_tests/tables/tests_sched.py
+++ b/test/trace_processor/diff_tests/tables/tests_sched.py
@@ -79,3 +79,44 @@
         81473010341386
         81473010352792
         """))
+
+  def test_sched_wakeup(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT * FROM spurious_sched_wakeup
+        ORDER BY ts LIMIT 10
+        """,
+        out=Csv("""
+        "id","type","ts","thread_state_id","irq_context","utid","waker_utid"
+        0,"spurious_sched_wakeup",1735850782904,395,0,230,1465
+        1,"spurious_sched_wakeup",1736413914899,852,0,230,1467
+        2,"spurious_sched_wakeup",1736977755745,1261,0,230,1469
+        3,"spurious_sched_wakeup",1737046900004,1434,0,1472,1473
+        4,"spurious_sched_wakeup",1737047159060,1463,0,1474,1472
+        5,"spurious_sched_wakeup",1737081636170,2721,0,1214,1319
+        6,"spurious_sched_wakeup",1737108696536,4684,0,501,557
+        7,"spurious_sched_wakeup",1737153309978,6080,0,11,506
+        8,"spurious_sched_wakeup",1737165240546,6562,0,565,499
+        9,"spurious_sched_wakeup",1737211563344,8645,0,178,1195
+        """))
+
+  def test_raw_common_flags(self):
+    return DiffTestBlueprint(
+        trace=DataPath('sched_wakeup_trace.atr'),
+        query="""
+        SELECT * FROM raw WHERE common_flags != 0 ORDER BY ts LIMIT 10
+        """,
+        out=Csv("""
+        "id","type","ts","name","cpu","utid","arg_set_id","common_flags"
+        3,"ftrace_event",1735489788930,"sched_waking",0,300,4,1
+        4,"ftrace_event",1735489812571,"sched_waking",0,300,5,1
+        5,"ftrace_event",1735489833977,"sched_waking",1,305,6,1
+        8,"ftrace_event",1735489876788,"sched_waking",1,297,9,1
+        9,"ftrace_event",1735489879097,"sched_waking",0,304,10,1
+        12,"ftrace_event",1735489933912,"sched_waking",0,428,13,1
+        14,"ftrace_event",1735489972385,"sched_waking",1,232,15,1
+        17,"ftrace_event",1735489999987,"sched_waking",1,232,15,1
+        19,"ftrace_event",1735490039439,"sched_waking",1,298,18,1
+        20,"ftrace_event",1735490042084,"sched_waking",1,298,19,1
+        """))
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/check_sql_modules.py b/tools/check_sql_modules.py
index 051f1a9..8037fca 100755
--- a/tools/check_sql_modules.py
+++ b/tools/check_sql_modules.py
@@ -16,32 +16,30 @@
 # This tool checks that every SQL object created without prefix
 # 'internal_' is documented with proper schema.
 
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-
 import os
 import sys
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 sys.path.append(os.path.join(ROOT_DIR))
-FILE_DIR = ROOT_DIR
 
-from python.generators.stdlib_docs.stdlib import *
+from python.generators.stdlib_docs.parse import parse_file_to_dict
 
 
 def main():
-
   errors = []
-  metrics_sources = os.path.join(FILE_DIR, "src", "trace_processor", "stdlib")
+  metrics_sources = os.path.join(ROOT_DIR, "src", "trace_processor", "stdlib")
   for root, _, files in os.walk(metrics_sources, topdown=True):
     for f in files:
       path = os.path.join(root, f)
-      if path.endswith(".sql"):
-        with open(path) as f:
-          sql = f.read()
-        errors += parse_file_to_dict(path, sql)[1]
-  sys.stderr.write("\n\n".join(errors))
+      if not path.endswith(".sql"):
+        continue
+      with open(path, 'r') as f:
+        sql = f.read()
+
+      res = parse_file_to_dict(path, sql)
+      errors += res if isinstance(res, list) else []
+  sys.stderr.write("\n".join(errors))
+  sys.stderr.write("\n")
   return 0 if not errors else 1
 
 
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 5317914..b154ee3 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -103,6 +103,8 @@
     '//protos/perfetto/trace:perfetto_trace_protos',
     '//src/trace_processor:demangle',
     '//src/trace_processor:trace_processor_shell',
+    '//src/traced/probes:traced_probes',
+    '//src/traced/service:traced',
 ]
 
 target_vendor_available = [
diff --git a/tools/gen_bazel b/tools/gen_bazel
index 3121aa8..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': {
@@ -787,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_stdlib_docs_json.py b/tools/gen_stdlib_docs_json.py
index c89b7a7..1f2e5be 100755
--- a/tools/gen_stdlib_docs_json.py
+++ b/tools/gen_stdlib_docs_json.py
@@ -23,7 +23,7 @@
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 sys.path.append(os.path.join(ROOT_DIR))
 
-from python.generators.stdlib_docs.stdlib import *
+from python.generators.stdlib_docs.parse import parse_file_to_dict
 
 
 def main():
@@ -70,7 +70,8 @@
     module_name = path.split("/")[0]
     import_key = path.split(".sql")[0].replace("/", ".")
 
-    docs = parse_file_to_dict(path, sql)[0]
+    docs = parse_file_to_dict(path, sql)
+    assert isinstance(docs, dict)
     if not any(docs.values()):
       continue
     file_dict = {'import_key': import_key, **docs}
diff --git a/tools/gen_tp_table_docs.py b/tools/gen_tp_table_docs.py
index 97e183e..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.
 
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 a9db452..013be00 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -351,12 +351,17 @@
 class HttpHandler(http.server.SimpleHTTPRequestHandler):
 
   def end_headers(self):
-    self.send_header('Access-Control-Allow-Origin', '*')
-    return super().end_headers()
+    self.send_header('Access-Control-Allow-Origin', self.server.allow_origin)
+    self.send_header('Cache-Control', 'no-cache')
+    super().end_headers()
 
   def do_GET(self):
-    self.server.last_request = self.path
-    return super().do_GET()
+    if self.path != '/' + self.server.expected_fname:
+      self.send_error(404, "File not found")
+      return
+
+    self.server.fname_get_completed = True
+    super().do_GET()
 
   def do_POST(self):
     self.send_error(404, "File not found")
@@ -382,9 +387,15 @@
   help = 'Output file or directory (default: %s)' % default_out_dir_str
   parser.add_argument('-o', '--out', default=default_out_dir, help=help)
 
-  help = 'Don\'t open in the browser'
+  help = 'Don\'t open or serve the trace'
   parser.add_argument('-n', '--no-open', action='store_true', help=help)
 
+  help = 'Don\'t open in browser, but still serve trace (good for remote use)'
+  parser.add_argument('--no-open-browser', action='store_true', help=help)
+
+  help = 'The web address used to open trace files'
+  parser.add_argument('--origin', default='https://ui.perfetto.dev', help=help)
+
   help = 'Force the use of the sideloaded binaries rather than system daemons'
   parser.add_argument('--sideload', action='store_true', help=help)
 
@@ -627,7 +638,8 @@
   if not args.no_open:
     prt('\n')
     prt('Opening the trace (%s) in the browser' % host_file)
-    open_trace_in_browser(host_file)
+    open_browser = not args.no_open_browser
+    open_trace_in_browser(host_file, open_browser, args.origin)
 
 
 def prt(msg, colors=ANSI.END):
@@ -655,17 +667,24 @@
     sys.exit(1)
 
 
-def open_trace_in_browser(path):
+def open_trace_in_browser(path, open_browser, origin):
   # We reuse the HTTP+RPC port because it's the only one allowed by the CSP.
   PORT = 9001
+  path = os.path.abspath(path)
   os.chdir(os.path.dirname(path))
   fname = os.path.basename(path)
   socketserver.TCPServer.allow_reuse_address = True
   with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd:
-    webbrowser.open_new_tab(
-        'https://ui.perfetto.dev/#!/?url=http://127.0.0.1:%d/%s' %
-        (PORT, fname))
-    while httpd.__dict__.get('last_request') != '/' + fname:
+    address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}'
+    if open_browser:
+      webbrowser.open_new_tab(address)
+    else:
+      print(f'Open URL in browser: {address}')
+
+    httpd.expected_fname = fname
+    httpd.fname_get_completed = None
+    httpd.allow_origin = origin
+    while httpd.fname_get_completed is None:
       httpd.handle_request()
 
 
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/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 0435422..c26ee4f 100644
--- a/ui/src/assets/common.scss
+++ b/ui/src/assets/common.scss
@@ -187,10 +187,6 @@
   }
 }
 
-.query-table-container {
-  width: 100%;
-}
-
 @mixin table-font-size {
   font-size: 14px;
   line-height: 18px;
@@ -270,8 +266,9 @@
   font-size: 14px;
   border: 0;
   thead td {
-    position: sticky;
-    top: 0;
+    // TODO(stevegolton): Get sticky working again.
+    // position: sticky;
+    // top: 0;
     background-color: hsl(214, 22%, 90%);
     color: #262f3c;
     text-align: center;
@@ -471,17 +468,7 @@
 
 .details-panel-container {
   box-shadow: #0000003b 0px 0px 3px 1px;
-  position: relative;
-  overflow-x: hidden;
-  overflow-y: auto;
-  flex: 1 1 auto;
-  // TODO(hjd): This causes the sticky header to flicker when scrolling.
-  // Is will-change necessary in the details panel?
-  // will-change: transform;
-  display: grid;
-  grid-template-columns: 1fr;
-  grid-template-rows: 1fr;
-  grid-template-areas: "space";
+  overflow: auto;
 }
 
 .pinned-panel-container {
@@ -520,9 +507,10 @@
   position: relative; // Otherwise canvas covers panel dom.
 
   &.sticky {
-    position: sticky;
+    // TODO(stevegolton): Get sticky working again.
+    // position: sticky;
     z-index: 3;
-    top: 0;
+    // top: 0;
     background-color: hsl(215, 22%, 19%);
   }
 }
@@ -555,6 +543,9 @@
 }
 
 header.overview {
+  position: sticky;
+  top: 0;
+  left: 0;
   display: flex;
   align-content: baseline;
   background-color: hsl(213, 22%, 82%);
diff --git a/ui/src/assets/details.scss b/ui/src/assets/details.scss
index 9ba93b3..50e0d20 100644
--- a/ui/src/assets/details.scss
+++ b/ui/src/assets/details.scss
@@ -12,92 +12,87 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-.details-content {
+.handle {
+  background-color: hsl(215, 1%, 95%);
+  border: 1px solid rgba(0, 0, 0, 0.1);
+  border-bottom: none;
+  cursor: row-resize;
+  // Disable user selection since this handle is draggable to resize the
+  // bottom panels.
+  user-select: none;
+  height: 28px;
+  min-height: 28px;
   display: grid;
-  grid-template-rows: auto 1fr;
+  grid-auto-columns: 1fr 60px;
+  grid-template-areas: "tabs buttons";
 
-  .handle {
-    background-color: hsl(215, 1%, 95%);
-    border: 1px solid rgba(0, 0, 0, 0.1);
-    border-bottom: none;
-    cursor: row-resize;
-    // Disable user selection since this handle is draggable to resize the
-    // bottom panels.
-    user-select: none;
-    height: 28px;
-    min-height: 28px;
-    display: grid;
-    grid-auto-columns: 1fr 60px;
-    grid-template-areas: "tabs buttons";
+  .tabs {
+    display: flex;
+    grid-area: tabs;
+    overflow: hidden;
 
-    .tabs {
-      display: flex;
-      grid-area: tabs;
+    .tab {
+      font-family: "Roboto Condensed", sans-serif;
+      color: #3c4b5d;
+      padding: 3px 10px 0 10px;
+      margin-top: 3px;
+      font-size: 13px;
+      border-radius: 3px 3px 0 0;
+      background-color: #0000000f;
+      border-right: solid 1px hsla(0, 0%, 75%, 1);
       overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      z-index: 5;
+      box-shadow: #0000003b 0px 0px 3px 1px;
 
-      .tab {
-        font-family: "Roboto Condensed", sans-serif;
-        color: #3c4b5d;
-        padding: 3px 10px 0 10px;
-        margin-top: 3px;
-        font-size: 13px;
-        border-radius: 3px 3px 0 0;
-        background-color: #0000000f;
-        border-right: solid 1px hsla(0, 0%, 75%, 1);
-        overflow: hidden;
-        white-space: nowrap;
-        text-overflow: ellipsis;
-        z-index: 5;
-        box-shadow: #0000003b 0px 0px 3px 1px;
-
-        &[active] {
-          background-color: white;
-          &:hover {
-            cursor: default;
-            background-color: white;
-          }
-        }
-
-        &:hover {
-          cursor: pointer;
-          background-color: hsla(0, 0%, 85%, 1);
-        }
-
-        &:nth-child(1) {
-          margin-left: 3px;
-        }
-      }
-    }
-
-    i.material-icons {
-      font-size: 24px;
-      margin-right: 5px;
-      margin-top: 1px;
-      &:hover {
-        cursor: pointer;
-      }
-      &[disabled] {
-        color: rgb(219, 219, 219);
+      &[active] {
+        background-color: white;
         &:hover {
           cursor: default;
+          background-color: white;
         }
       }
-    }
 
-    .buttons {
-      grid-area: buttons;
-      text-align: right;
-    }
+      &:hover {
+        cursor: pointer;
+        background-color: hsla(0, 0%, 85%, 1);
+      }
 
-    .handle-title {
-      font-family: "Roboto Condensed", sans-serif;
-      font-weight: 300;
-      color: #3c4b5d;
-      margin-left: 5px;
-      padding: 5px;
-      font-size: 13px;
+      &:nth-child(1) {
+        margin-left: 3px;
+      }
     }
   }
+
+  i.material-icons {
+    font-size: 24px;
+    margin-right: 5px;
+    margin-top: 1px;
+    &:hover {
+      cursor: pointer;
+    }
+    &[disabled] {
+      color: rgb(219, 219, 219);
+      &:hover {
+        cursor: default;
+      }
+    }
+  }
+
+  .buttons {
+    grid-area: buttons;
+    text-align: right;
+  }
+
+  .handle-title {
+    font-family: "Roboto Condensed", sans-serif;
+    font-weight: 300;
+    color: #3c4b5d;
+    margin-left: 5px;
+    padding: 5px;
+    font-size: 13px;
+  }
 }
 
 .details-panel {
@@ -293,6 +288,7 @@
 
 .details-table-multicolumn {
   display: flex;
+  user-select: 'text';
 }
 
 .flow-link:hover {
@@ -401,16 +397,12 @@
 }
 
 .log-panel {
-  width: 100%;
-  height: 100%;
-  display: grid;
-  grid-template-rows: auto 1fr;
-  font-family: "Roboto Condensed", sans-serif;
-  user-select: text;
+  display: contents;
 
   header {
     position: sticky;
     top: 0;
+    left: 0;
     z-index: 1;
     background-color: white;
     color: #3c4b5d;
@@ -619,14 +611,12 @@
 }
 
 .ftrace-panel {
-  display: grid;
-  grid-template-rows: auto 1fr;
-  font-family: "Roboto Condensed", sans-serif;
-  user-select: text;
+  display: contents;
 
   .sticky {
     position: sticky;
     top: 0;
+    left: 0;
     z-index: 1;
     background-color: white;
     color: #3c4b5d;
diff --git a/ui/src/base/bigint_math.ts b/ui/src/base/bigint_math.ts
new file mode 100644
index 0000000..45fab7b
--- /dev/null
+++ b/ui/src/base/bigint_math.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.
+
+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 value x where 2^x is not greater than n.
+  static log2(n: bigint): number {
+    let result = 1n;
+    let log2 = 0;
+    while ((result << 1n) <= n) {
+      result <<= 1n;
+      ++log2;
+    }
+    return log2;
+  }
+
+  // Returns the integral multiple of step which is closest to n.
+  // If step is less than or equal to 0, returns n.
+  static quant(n: bigint, step: bigint): bigint {
+    step = BigintMath.max(1n, step);
+    const halfStep = step / 2n;
+    return step * ((n + halfStep) / step);
+  }
+
+  // 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 quantFloor(n: bigint, step: bigint): bigint {
+    step = BigintMath.max(1n, step);
+    return step * (n / step);
+  }
+
+  // Returns the smallest integral multiple of step which is not smaller than n.
+  // If step is less than or equal to 0, returns n.
+  static quantCeil(n: bigint, step: bigint): bigint {
+    step = BigintMath.max(1n, step);
+    const remainder = n % step;
+    if (remainder === 0n) {
+      return n;
+    }
+    const quotient = n / step;
+    return (quotient + 1n) * step;
+  }
+
+  // Returns the greater of a and b.
+  static max(a: bigint, b: bigint): bigint {
+    return a > b ? a : b;
+  }
+
+  // Returns 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;
+  }
+
+  // Return the ratio between two bigints as a number.
+  static ratio(dividend: bigint, divisor: bigint): number {
+    return Number(dividend) / Number(divisor);
+  }
+}
diff --git a/ui/src/base/bigint_math_unittest.ts b/ui/src/base/bigint_math_unittest.ts
new file mode 100644
index 0000000..33e7179
--- /dev/null
+++ b/ui/src/base/bigint_math_unittest.ts
@@ -0,0 +1,224 @@
+// 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', () => {
+  describe('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('bitFloor', () => {
+    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('log2', () => {
+    it('calcs exact powers of 2', () => {
+      expect(BIM.log2(1n)).toBe(0);
+      expect(BIM.log2(2n)).toBe(1);
+      expect(BIM.log2(4n)).toBe(2);
+      expect(BIM.log2(4294967296n)).toBe(32);
+      expect(BIM.log2(2305843009213693952n)).toBe(61);
+    });
+
+    it('rounds non powers of 2 down to nearest power of 2', () => {
+      expect(BIM.log2(3n)).toBe(1);
+      expect(BIM.log2(11n)).toBe(3);
+      expect(BIM.log2(33n)).toBe(5);
+      expect(BIM.log2(63n)).toBe(5);
+      expect(BIM.log2(1234567890123456789n)).toBe(60);
+    });
+
+    it('returns 0 for 0n negative numbers', () => {
+      expect(BIM.log2(0n)).toBe(0);
+      expect(BIM.log2(-123n)).toBe(0);
+    });
+  });
+
+  describe('quant', () => {
+    it('should round an int to the nearest multiple of a stepsize', () => {
+      expect(BIM.quant(0n, 2n)).toEqual(0n);
+      expect(BIM.quant(1n, 2n)).toEqual(2n);
+      expect(BIM.quant(2n, 2n)).toEqual(2n);
+      expect(BIM.quant(3n, 2n)).toEqual(4n);
+      expect(BIM.quant(4n, 2n)).toEqual(4n);
+
+      expect(BIM.quant(0n, 3n)).toEqual(0n);
+      expect(BIM.quant(1n, 3n)).toEqual(0n);
+      expect(BIM.quant(2n, 3n)).toEqual(3n);
+      expect(BIM.quant(3n, 3n)).toEqual(3n);
+      expect(BIM.quant(4n, 3n)).toEqual(3n);
+      expect(BIM.quant(5n, 3n)).toEqual(6n);
+      expect(BIM.quant(6n, 3n)).toEqual(6n);
+    });
+
+    it('should return value if stepsize is smaller than 1', () => {
+      expect(BIM.quant(123n, 0n)).toEqual(123n);
+      expect(BIM.quant(123n, -10n)).toEqual(123n);
+    });
+  });
+
+  describe('quantFloor', () => {
+    it('should quantize a number to the nearest multiple of a stepsize', () => {
+      expect(BIM.quantFloor(10n, 2n)).toEqual(10n);
+      expect(BIM.quantFloor(11n, 2n)).toEqual(10n);
+      expect(BIM.quantFloor(12n, 2n)).toEqual(12n);
+      expect(BIM.quantFloor(13n, 2n)).toEqual(12n);
+
+      expect(BIM.quantFloor(9n, 4n)).toEqual(8n);
+      expect(BIM.quantFloor(10n, 4n)).toEqual(8n);
+      expect(BIM.quantFloor(11n, 4n)).toEqual(8n);
+      expect(BIM.quantFloor(12n, 4n)).toEqual(12n);
+      expect(BIM.quantFloor(13n, 4n)).toEqual(12n);
+    });
+
+    it('should return value if stepsize is smaller than 1', () => {
+      expect(BIM.quantFloor(123n, 0n)).toEqual(123n);
+      expect(BIM.quantFloor(123n, -10n)).toEqual(123n);
+    });
+  });
+
+  describe('quantCeil', () => {
+    it('should round an int up to the nearest multiple of a stepsize', () => {
+      expect(BIM.quantCeil(10n, 2n)).toEqual(10n);
+      expect(BIM.quantCeil(11n, 2n)).toEqual(12n);
+      expect(BIM.quantCeil(12n, 2n)).toEqual(12n);
+      expect(BIM.quantCeil(13n, 2n)).toEqual(14n);
+
+      expect(BIM.quantCeil(9n, 4n)).toEqual(12n);
+      expect(BIM.quantCeil(10n, 4n)).toEqual(12n);
+      expect(BIM.quantCeil(11n, 4n)).toEqual(12n);
+      expect(BIM.quantCeil(12n, 4n)).toEqual(12n);
+      expect(BIM.quantCeil(13n, 4n)).toEqual(16n);
+    });
+
+    it('should return value if stepsize is smaller than 1', () => {
+      expect(BIM.quantCeil(123n, 0n)).toEqual(123n);
+      expect(BIM.quantCeil(123n, -10n)).toEqual(123n);
+    });
+  });
+
+  describe('quantRound', () => {
+    it('should quantize a number to the nearest multiple of a stepsize', () => {
+      expect(BIM.quant(0n, 2n)).toEqual(0n);
+      expect(BIM.quant(1n, 2n)).toEqual(2n);
+      expect(BIM.quant(2n, 2n)).toEqual(2n);
+      expect(BIM.quant(3n, 2n)).toEqual(4n);
+      expect(BIM.quant(4n, 2n)).toEqual(4n);
+
+      expect(BIM.quant(0n, 3n)).toEqual(0n);
+      expect(BIM.quant(1n, 3n)).toEqual(0n);
+      expect(BIM.quant(2n, 3n)).toEqual(3n);
+      expect(BIM.quant(3n, 3n)).toEqual(3n);
+      expect(BIM.quant(4n, 3n)).toEqual(3n);
+      expect(BIM.quant(5n, 3n)).toEqual(6n);
+      expect(BIM.quant(6n, 3n)).toEqual(6n);
+    });
+
+    it('should return value if stepsize is smaller than 1', () => {
+      expect(BIM.quant(123n, 0n)).toEqual(123n);
+      expect(BIM.quant(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');
+    });
+  });
+
+  describe('ratio', () => {
+    it('should return ratio as number', () => {
+      expect(BIM.ratio(0n, 1n)).toBeCloseTo(0);
+      expect(BIM.ratio(1n, 1n)).toBeCloseTo(1);
+      expect(BIM.ratio(1n, 2n)).toBeCloseTo(0.5);
+      expect(BIM.ratio(1n, 100n)).toBeCloseTo(0.01);
+      expect(
+          BIM.ratio(
+              987654321098765432109876543210n, 123456789012345678901234567890n))
+          .toBeCloseTo(8);
+      expect(
+          BIM.ratio(
+              123456789012345678901234567890n, 987654321098765432109876543210n))
+          .toBeCloseTo(0.125, 3);
+    });
+  });
+});
diff --git a/ui/src/base/binary_search.ts b/ui/src/base/binary_search.ts
index ef352c2..2b78627 100644
--- a/ui/src/base/binary_search.ts
+++ b/ui/src/base/binary_search.ts
@@ -12,12 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-
-type Numbers = Float64Array|Uint32Array|number[];
+type Numeric = number|bigint;
 type Range = [number, number];
 
-function searchImpl(
-    haystack: Numbers, needle: number, i: number, j: number): number {
+function searchImpl<T extends Numeric>(
+    haystack: ArrayLike<T>, needle: T, i: number, j: number): number {
   if (i === j) return -1;
   if (i + 1 === j) {
     return (needle >= haystack[i]) ? i : -1;
@@ -32,8 +31,8 @@
   }
 }
 
-function searchRangeImpl(
-    haystack: Numbers, needle: number, i: number, j: number): Range {
+function searchRangeImpl<T extends Numeric>(
+    haystack: ArrayLike<T>, needle: T, i: number, j: number): Range {
   if (i === j) return [i, j];
   if (i + 1 === j) {
     if (haystack[i] <= needle) {
@@ -57,29 +56,33 @@
   }
 }
 
-export function search(haystack: Numbers, needle: number): number {
+// Given a sorted array of numeric values |haystack| and a |needle|, return the
+// index of the last element of |haystack| which is less than or equal to
+// |needle|, or -1 if all elements of |haystack| are greater than |needle|.
+export function search<T extends Numeric>(
+    haystack: ArrayLike<T>, needle: T): number {
   return searchImpl(haystack, needle, 0, haystack.length);
 }
 
-// Given a sorted array of numbers (|haystack|) and a |needle| return the
-// half open range [i, j) of indexes where |haystack| is equal to needle.
-export function searchEq(
-    haystack: Numbers, needle: number, optRange?: Range): Range {
+// Given a sorted array of numeric values (|haystack|) return the half open
+// range [i, j) of indices where |haystack| is equal to needle.
+export function searchEq<T extends Numeric>(
+    haystack: ArrayLike<T>, needle: T, optRange?: Range): Range {
   const range = searchRange(haystack, needle, optRange);
   const [i, j] = range;
   if (haystack[i] === needle) return range;
   return [j, j];
 }
 
-// Given a sorted array of numbers (|haystack|) and a |needle| return the
+// Given a sorted array of numeric values (|haystack|) and a |needle| return the
 // smallest half open range [i, j) of indexes which contains |needle|.
-export function searchRange(
-    haystack: Numbers, needle: number, optRange?: Range): Range {
+export function searchRange<T extends Numeric>(
+    haystack: ArrayLike<T>, needle: T, optRange?: Range): Range {
   const [left, right] = optRange ? optRange : [0, haystack.length];
   return searchRangeImpl(haystack, needle, left, right);
 }
 
-// Given a sorted array of numbers (|haystack|) and a |needle| return a
+// Given a sorted array of numeric values (|haystack|) and a |needle| return a
 // pair of indexes [i, j] such that:
 // If there is at least one element in |haystack| smaller than |needle|
 // i is the index of the largest such number otherwise -1;
@@ -88,7 +91,8 @@
 //
 // So we try to get the indexes of the two data points around needle
 // or -1 if there is no such datapoint.
-export function searchSegment(haystack: Numbers, needle: number): Range {
+export function searchSegment<T extends Numeric>(
+    haystack: ArrayLike<T>, needle: T): Range {
   if (!haystack.length) return [-1, -1];
 
   const left = search(haystack, needle);
diff --git a/ui/src/base/binary_search_unittest.ts b/ui/src/base/binary_search_unittest.ts
index c941e02..917c810 100644
--- a/ui/src/base/binary_search_unittest.ts
+++ b/ui/src/base/binary_search_unittest.ts
@@ -12,21 +12,26 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import {search, searchEq, searchRange, searchSegment} from './binary_search';
+import {
+  search,
+  searchEq,
+  searchRange,
+  searchSegment,
+} from './binary_search';
 
 test('binarySearch', () => {
-  expect(search(Float64Array.of(), 100)).toEqual(-1);
-  expect(search(Float64Array.of(42), 42)).toEqual(0);
-  expect(search(Float64Array.of(42), 43)).toEqual(0);
-  expect(search(Float64Array.of(42), 41)).toEqual(-1);
-  expect(search(Float64Array.of(42, 43), 42)).toEqual(0);
-  expect(search(Float64Array.of(42, 43), 43)).toEqual(1);
-  expect(search(Float64Array.of(42, 43), 44)).toEqual(1);
-  expect(search(Float64Array.of(42, 43, 44), 41)).toEqual(-1);
-  expect(search(Float64Array.of(42, 43, 44), 42)).toEqual(0);
-  expect(search(Float64Array.of(42, 43, 44), 43)).toEqual(1);
-  expect(search(Float64Array.of(42, 43, 44), 44)).toEqual(2);
-  expect(search(Float64Array.of(42, 43, 44), 45)).toEqual(2);
+  expect(search([], 100)).toEqual(-1);
+  expect(search([42], 42)).toEqual(0);
+  expect(search([42], 43)).toEqual(0);
+  expect(search([42], 41)).toEqual(-1);
+  expect(search([42, 43], 42)).toEqual(0);
+  expect(search([42, 43], 43)).toEqual(1);
+  expect(search([42, 43], 44)).toEqual(1);
+  expect(search([42, 43, 44], 41)).toEqual(-1);
+  expect(search([42, 43, 44], 42)).toEqual(0);
+  expect(search([42, 43, 44], 43)).toEqual(1);
+  expect(search([42, 43, 44], 44)).toEqual(2);
+  expect(search([42, 43, 44], 45)).toEqual(2);
 });
 
 test('searchEq', () => {
@@ -58,8 +63,9 @@
 
 test('searchSegment', () => {
   expect(searchSegment(Float64Array.of(), 100)).toEqual([-1, -1]);
-  expect(searchSegment(Float64Array.of(42), 41)).toEqual([-1, 0]);
-  expect(searchSegment(Float64Array.of(42), 42)).toEqual([0, -1]);
-  expect(searchSegment(Float64Array.of(42), 43)).toEqual([0, -1]);
-  expect(searchSegment(Float64Array.of(42, 44), 42)).toEqual([0, 1]);
+  expect(searchSegment([42], 41)).toEqual([-1, 0]);
+  expect(searchSegment([42], 42)).toEqual([0, -1]);
+  expect(searchSegment([42], 43)).toEqual([0, -1]);
+  expect(searchSegment([42, 44], 42)).toEqual([0, 1]);
+  expect(searchSegment([1, 2, 2, 3], 2)).toEqual([2, 3]);
 });
diff --git a/ui/src/base/math_utils.ts b/ui/src/base/math_utils.ts
deleted file mode 100644
index f1c1816..0000000
--- a/ui/src/base/math_utils.ts
+++ /dev/null
@@ -1,23 +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.
-
-// Round a number up to the nearest stepsize.
-export function roundUpNearest(val: number, stepsize: number): number {
-  return stepsize * Math.ceil(val / stepsize);
-}
-
-// Round a number down to the nearest stepsize.
-export function roundDownNearest(val: number, stepsize: number): number {
-  return stepsize * Math.floor(val / stepsize);
-}
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..7b8f462 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;
 }
 
@@ -458,6 +458,15 @@
     }
   },
 
+  maybeSetPendingDeeplink(
+      state: StateDraft, args: {ts?: string, dur?: string, tid?: string}) {
+    state.pendingDeeplink = args;
+  },
+
+  clearPendingDeeplink(state: StateDraft, _: {}) {
+    state.pendingDeeplink = undefined;
+  },
+
   // TODO(hjd): engine.ready should be a published thing. If it's part
   // of the state it interacts badly with permalinks.
   setEngineReady(
@@ -552,7 +561,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 +572,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 +615,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 +672,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 +685,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 +695,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 +705,8 @@
   selectPerfSamples(state: StateDraft, args: {
     id: number,
     upid: number,
-    leftTs: number,
-    rightTs: number,
+    leftTs: TPTime,
+    rightTs: TPTime,
     type: ProfileType
   }): void {
     state.currentSelection = {
@@ -714,8 +719,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 +728,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: '',
@@ -740,7 +745,7 @@
   },
 
   selectCpuProfileSample(
-      state: StateDraft, args: {id: number, utid: number, ts: number}): void {
+      state: StateDraft, args: {id: number, utid: number, ts: TPTime}): void {
     state.currentSelection = {
       kind: 'CPU_PROFILE_SAMPLE',
       id: args.id,
@@ -784,16 +789,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 +915,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 +1045,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/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..fda88c8
--- /dev/null
+++ b/ui/src/common/high_precision_time.ts
@@ -0,0 +1,279 @@
+// 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';
+export type Timeish = HighPrecisionTime|TPTime;
+
+// 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.gt(b) ? a : b;
+  }
+
+  static min(a: HighPrecisionTime, b: HighPrecisionTime): HighPrecisionTime {
+    return a.lt(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);
+  }
+
+  sub(other: HighPrecisionTime): HighPrecisionTime {
+    return new HighPrecisionTime(
+        this.base - other.base, this.offset - other.offset);
+  }
+
+  subTPTime(ts: TPTime): HighPrecisionTime {
+    return this.addTPTime(-ts);
+  }
+
+  subNanos(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
+  eq(other: Timeish, epsilon: number = 1e-6): boolean {
+    const x = HighPrecisionTime.fromHPTimeOrTPTime(other);
+    return Math.abs(this.sub(x).nanos) < epsilon;
+  }
+
+  private static fromHPTimeOrTPTime(x: HighPrecisionTime|
+                                    TPTime): HighPrecisionTime {
+    if (x instanceof HighPrecisionTime) {
+      return x;
+    } else if (typeof x === 'bigint') {
+      return HighPrecisionTime.fromTPTime(x);
+    } else {
+      const y: never = x;
+      throw new Error(`Invalid type ${y}`);
+    }
+  }
+
+  lt(other: Timeish): boolean {
+    const x = HighPrecisionTime.fromHPTimeOrTPTime(other);
+    if (this.base < x.base) {
+      return true;
+    } else if (this.base === x.base) {
+      return this.offset < x.offset;
+    } else {
+      return false;
+    }
+  }
+
+  lte(other: Timeish): boolean {
+    if (this.eq(other)) {
+      return true;
+    } else {
+      return this.lt(other);
+    }
+  }
+
+  gt(other: Timeish): boolean {
+    return !this.lte(other);
+  }
+
+  gte(other: Timeish): boolean {
+    return !this.lt(other);
+  }
+
+  clamp(lower: HighPrecisionTime, upper: HighPrecisionTime): HighPrecisionTime {
+    if (this.lt(lower)) {
+      return lower;
+    } else if (this.gt(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)}`;
+    }
+  }
+
+  abs(): HighPrecisionTime {
+    if (this.base >= 0n) {
+      return this;
+    }
+    const newBase = -this.base;
+    const newOffset = -this.offset;
+    return new HighPrecisionTime(newBase, newOffset);
+  }
+}
+
+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.lte(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.sub(this.start);
+  }
+
+  get midpoint(): HighPrecisionTime {
+    return this.start.add(this.end).divide(2);
+  }
+
+  equals(other: Span<HighPrecisionTime>): boolean {
+    return this.start.eq(other.start) && this.end.eq(other.end);
+  }
+
+  contains(x: HighPrecisionTime|Span<HighPrecisionTime>): boolean {
+    if (x instanceof HighPrecisionTime) {
+      return this.start.lte(x) && x.lt(this.end);
+    } else {
+      return this.start.lte(x.start) && x.end.lte(this.end);
+    }
+  }
+
+  intersects(x: Span<HighPrecisionTime>): boolean {
+    return !(x.end.lte(this.start) || x.start.gte(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.sub(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..ae25b01
--- /dev/null
+++ b/ui/src/common/high_precision_time_unittest.ts
@@ -0,0 +1,326 @@
+// 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.sub(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 gte comparisions', () => {
+    const time = mkTime('1.2');
+    expect(time.gte(mkTime('0.5'))).toBeTruthy();
+    expect(time.gte(mkTime('1.1'))).toBeTruthy();
+    expect(time.gte(mkTime('1.2'))).toBeTruthy();
+    expect(time.gte(mkTime('1.5'))).toBeFalsy();
+    expect(time.gte(mkTime('5.5'))).toBeFalsy();
+  });
+
+  it('should perform gt comparisions', () => {
+    const time = mkTime('1.2');
+    expect(time.gt(mkTime('0.5'))).toBeTruthy();
+    expect(time.gt(mkTime('1.1'))).toBeTruthy();
+    expect(time.gt(mkTime('1.2'))).toBeFalsy();
+    expect(time.gt(mkTime('1.5'))).toBeFalsy();
+    expect(time.gt(mkTime('5.5'))).toBeFalsy();
+  });
+
+  it('should perform lt comparisions', () => {
+    const time = mkTime('1.2');
+    expect(time.lt(mkTime('0.5'))).toBeFalsy();
+    expect(time.lt(mkTime('1.1'))).toBeFalsy();
+    expect(time.lt(mkTime('1.2'))).toBeFalsy();
+    expect(time.lt(mkTime('1.5'))).toBeTruthy();
+    expect(time.lt(mkTime('5.5'))).toBeTruthy();
+  });
+
+  it('should perform lte comparisions', () => {
+    const time = mkTime('1.2');
+    expect(time.lte(mkTime('0.5'))).toBeFalsy();
+    expect(time.lte(mkTime('1.1'))).toBeFalsy();
+    expect(time.lte(mkTime('1.2'))).toBeTruthy();
+    expect(time.lte(mkTime('1.5'))).toBeTruthy();
+    expect(time.lte(mkTime('5.5'))).toBeTruthy();
+  });
+
+  it('should detect equality', () => {
+    const time = new HPTime(1n, 0.2);
+    expect(time.eq(new HPTime(1n, 0.2))).toBeTruthy();
+    expect(time.eq(new HPTime(0n, 1.2))).toBeTruthy();
+    expect(time.eq(new HPTime(-100n, 101.2))).toBeTruthy();
+    expect(time.eq(new HPTime(1n, 0.3))).toBeFalsy();
+    expect(time.eq(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');
+  });
+
+  it('should calculate absolute', () => {
+    let result = mkTime('-0.7').abs();
+    expect(result.base).toEqual(0n);
+    expect(result.offset).toBeCloseTo(0.7);
+
+    result = mkTime('-1.3').abs();
+    expect(result.base).toEqual(1n);
+    expect(result.offset).toBeCloseTo(0.3);
+
+    result = mkTime('-100').abs();
+    expect(result.base).toEqual(100n);
+    expect(result.offset).toBeCloseTo(0);
+
+    result = mkTime('34.5345').abs();
+    expect(result.base).toEqual(34n);
+    expect(result.offset).toBeCloseTo(0.5345);
+  });
+});
+
+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/query_result.ts b/ui/src/common/query_result.ts
index e8d482d..90fb8f4 100644
--- a/ui/src/common/query_result.ts
+++ b/ui/src/common/query_result.ts
@@ -159,7 +159,7 @@
 
 // One row extracted from an SQL result:
 export interface Row {
-  [key: string]: ColumnType;
+  [key: string]: ColumnType|undefined;
 }
 
 // The methods that any iterator has to implement.
diff --git a/ui/src/common/recordingV2/recording_config_utils.ts b/ui/src/common/recordingV2/recording_config_utils.ts
index 2840b3c..d02da5a 100644
--- a/ui/src/common/recordingV2/recording_config_utils.ts
+++ b/ui/src/common/recordingV2/recording_config_utils.ts
@@ -79,9 +79,8 @@
 
 export function genTraceConfig(
     uiCfg: RecordConfig, targetInfo: TargetInfo): TraceConfig {
-  const androidApiLevel = (targetInfo.targetType === 'ANDROID') ?
-      targetInfo.androidApiLevel :
-      undefined;
+  const isAndroid = targetInfo.targetType === 'ANDROID';
+  const androidApiLevel = isAndroid ? targetInfo.androidApiLevel : undefined;
   const protoCfg = new TraceConfig();
   protoCfg.durationMs = uiCfg.durationMs;
 
@@ -96,8 +95,8 @@
 
   protoCfg.buffers.push(new BufferConfig());
   protoCfg.buffers.push(new BufferConfig());
-  protoCfg.buffers[1].sizeKb = slowBufSizeKb;
   protoCfg.buffers[0].sizeKb = fastBufSizeKb;
+  protoCfg.buffers[1].sizeKb = slowBufSizeKb;
 
   if (uiCfg.mode === 'STOP_WHEN_FULL') {
     protoCfg.buffers[0].fillPolicy = BufferConfig.FillPolicy.DISCARD;
@@ -131,6 +130,14 @@
   let procThreadAssociationFtrace = false;
   let trackInitialOomScore = false;
 
+  if (isAndroid) {
+    const ds = new TraceConfig.DataSource();
+    ds.config = new DataSourceConfig();
+    ds.config.targetBuffer = 1;
+    ds.config.name = 'android.packages_list';
+    protoCfg.dataSources.push(ds);
+  }
+
   if (uiCfg.cpuSched) {
     procThreadAssociationPolling = true;
     procThreadAssociationFtrace = true;
diff --git a/ui/src/common/search_data.ts b/ui/src/common/search_data.ts
index 0969c1d..2854d3c 100644
--- a/ui/src/common/search_data.ts
+++ b/ui/src/common/search_data.ts
@@ -13,14 +13,14 @@
 // limitations under the License.
 
 export interface SearchSummary {
-  tsStarts: Float64Array;
-  tsEnds: Float64Array;
+  tsStarts: BigInt64Array;
+  tsEnds: BigInt64Array;
   count: Uint8Array;
 }
 
 export interface CurrentSearchResults {
   sliceIds: Float64Array;
-  tsStarts: Float64Array;
+  tsStarts: BigInt64Array;
   utids: Float64Array;
   trackIds: string[];
   sources: string[];
diff --git a/ui/src/common/state.ts b/ui/src/common/state.ts
index d4d07b7..52855b8 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,9 @@
 // 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.
+// 32. Add pendingDeeplink.
+export const STATE_VERSION = 31;
 
 export const SCROLLING_TRACK_GROUP = 'ScrollingTracks';
 
@@ -268,8 +273,8 @@
 }
 
 export interface TraceTime {
-  startSec: number;
-  endSec: number;
+  start: TPTime;
+  end: TPTime;
 }
 
 export interface FrontendLocalState {
@@ -284,7 +289,7 @@
 export interface Note {
   noteType: 'DEFAULT';
   id: string;
-  timestamp: number;
+  timestamp: TPTime;
   color: string;
   text: string;
 }
@@ -311,14 +316,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 +331,7 @@
   kind: 'HEAP_PROFILE';
   id: number;
   upid: number;
-  ts: number;
+  ts: TPTime;
   type: ProfileType;
 }
 
@@ -334,16 +339,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;
@@ -354,7 +359,7 @@
   kind: 'CPU_PROFILE_SAMPLE';
   id: number;
   utid: number;
-  ts: number;
+  ts: TPTime;
 }
 
 export interface ChromeSliceSelection {
@@ -377,8 +382,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 {
@@ -511,6 +516,12 @@
   excludedNames: string[];
 }
 
+export interface PendingDeeplinkState {
+  ts?: string;
+  dur?: string;
+  tid?: string;
+}
+
 export interface State {
   version: number;
   nextId: string;
@@ -570,8 +581,8 @@
   // Hovered and focused events
   hoveredUtid: number;
   hoveredPid: number;
-  hoverCursorTimestamp: number;
-  hoveredNoteTimestamp: number;
+  hoverCursorTimestamp: TPTime;
+  hoveredNoteTimestamp: TPTime;
   highlightedSliceId: number;
   focusedFlowIdLeft: number;
   focusedFlowIdRight: number;
@@ -605,11 +616,15 @@
 
   // Omnibox info.
   omniboxState: OmniboxState;
+
+  // Pending deeplink which will happen when we first finish opening a
+  // trace.
+  pendingDeeplink?: PendingDeeplinkState;
 }
 
 export const defaultTraceTime = {
-  startSec: 0,
-  endSec: 10,
+  start: 0n,
+  end: BigInt(10e9),
 };
 
 export declare type RecordMode =
@@ -671,12 +686,13 @@
 }
 
 export function getBuiltinChromeCategoryList(): string[] {
-  // List of static Chrome categories, last updated at 2023-04-04 from HEAD of
+  // List of static Chrome categories, last updated at 2023-05-30 from HEAD of
   // Chromium's //base/trace_event/builtin_categories.h.
   return [
     'accessibility',
     'AccountFetcherService',
     'android_webview',
+    'android_webview.timeline',
     'aogh',
     'audio',
     'base',
diff --git a/ui/src/common/time.ts b/ui/src/common/time.ts
index ea5e9d8..a96778d 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,11 @@
   return `${sign < 0 ? '-' : ''}${Math.round(n * 10) / 10} ${units[u]}`;
 }
 
+export function tpTimeToString(time: TPTime) {
+  // TODO(stevegolton): Write a formatter to format bigint timestamps natively.
+  return timeToString(tpTimeToSeconds(time));
+}
+
 export function fromNs(ns: number) {
   return ns / 1e9;
 }
@@ -52,6 +56,11 @@
   return parts.join('.');
 }
 
+export function formatTPTime(time: TPTime) {
+  // TODO(stevegolton): Write a formatter to format bigint timestamps natively.
+  return formatTimestamp(tpTimeToSeconds(time));
+}
+
 // TODO(hjd): Rename to formatTimestampWithUnits
 // 1000000023ns -> "1s 23ns"
 export function timeToCode(sec: number): string {
@@ -77,44 +86,129 @@
   return result.slice(0, -1);
 }
 
+export function tpTimeToCode(time: TPTime) {
+  // TODO(stevegolton): Write a formatter to format bigint timestamps natively.
+  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/controller/aggregation/aggregation_controller.ts b/ui/src/controller/aggregation/aggregation_controller.ts
index f92f4fa..f5c855b 100644
--- a/ui/src/controller/aggregation/aggregation_controller.ts
+++ b/ui/src/controller/aggregation/aggregation_controller.ts
@@ -149,7 +149,12 @@
         } else if (item instanceof Uint8Array) {
           column.data[i] = internString('<Binary blob>');
         } else if (typeof item === 'bigint') {
-          // TODO(stevegolton) Handle potential loss of precision
+          // TODO(stevegolton) It would be nice to keep bigints as bigints for
+          // the purposes of aggregation, however the aggregation infrastructure
+          // is likely to be significantly reworked when we introduce EventSet,
+          // and the complexity of supporting bigints throughout the aggregation
+          // panels in it's current form is not worth it. Thus, we simply
+          // convert bigints to numbers.
           column.data[i] = Number(item);
         } else {
           column.data[i] = item;
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..d92090d 100644
--- a/ui/src/controller/flow_events_controller.ts
+++ b/ui/src/controller/flow_events_controller.ts
@@ -14,9 +14,8 @@
 
 import {Engine} from '../common/engine';
 import {featureFlags} from '../common/feature_flags';
-import {NUM, STR_NULL} from '../common/query_result';
+import {LONG, NUM, STR_NULL} from '../common/query_result';
 import {Area} from '../common/state';
-import {fromNs, toNs} from '../common/time';
 import {Flow, globals} from '../frontend/globals';
 import {publishConnectedFlows, publishSelectedFlows} from '../frontend/publish';
 import {
@@ -86,8 +85,8 @@
         beginSliceName: STR_NULL,
         beginSliceChromeCustomName: STR_NULL,
         beginSliceCategory: STR_NULL,
-        beginSliceStartTs: NUM,
-        beginSliceEndTs: NUM,
+        beginSliceStartTs: LONG,
+        beginSliceEndTs: LONG,
         beginDepth: NUM,
         beginThreadName: STR_NULL,
         beginProcessName: STR_NULL,
@@ -96,8 +95,8 @@
         endSliceName: STR_NULL,
         endSliceChromeCustomName: STR_NULL,
         endSliceCategory: STR_NULL,
-        endSliceStartTs: NUM,
-        endSliceEndTs: NUM,
+        endSliceStartTs: LONG,
+        endSliceEndTs: LONG,
         endDepth: NUM,
         endThreadName: STR_NULL,
         endProcessName: STR_NULL,
@@ -116,8 +115,8 @@
             it.beginSliceChromeCustomName;
         const beginSliceCategory =
             it.beginSliceCategory === null ? 'NULL' : it.beginSliceCategory;
-        const beginSliceStartTs = fromNs(it.beginSliceStartTs);
-        const beginSliceEndTs = fromNs(it.beginSliceEndTs);
+        const beginSliceStartTs = it.beginSliceStartTs;
+        const beginSliceEndTs = it.beginSliceEndTs;
         const beginDepth = it.beginDepth;
         const beginThreadName =
             it.beginThreadName === null ? 'NULL' : it.beginThreadName;
@@ -133,8 +132,8 @@
             it.endSliceChromeCustomName;
         const endSliceCategory =
             it.endSliceCategory === null ? 'NULL' : it.endSliceCategory;
-        const endSliceStartTs = fromNs(it.endSliceStartTs);
-        const endSliceEndTs = fromNs(it.endSliceEndTs);
+        const endSliceStartTs = it.endSliceStartTs;
+        const endSliceEndTs = it.endSliceEndTs;
         const endDepth = it.endDepth;
         const endThreadName =
             it.endThreadName === null ? 'NULL' : it.endThreadName;
@@ -241,8 +240,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 +267,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 f558a4a..7f4ecd9 100644
--- a/ui/src/controller/ftrace_controller.ts
+++ b/ui/src/controller/ftrace_controller.ts
@@ -13,9 +13,13 @@
 // limitations under the License.
 
 import {Engine} from '../common/engine';
-import {NUM, STR, STR_NULL} from '../common/query_result';
+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 {TimeSpan, toNsCeil, toNsFloor} from '../common/time';
+import {Span} from '../common/time';
 import {FtraceEvent, globals} from '../frontend/globals';
 import {publishFtracePanelData} from '../frontend/publish';
 import {ratelimit} from '../frontend/rate_limiters';
@@ -34,7 +38,7 @@
 
 export class FtraceController extends Controller<'main'> {
   private engine: Engine;
-  private oldSpan: TimeSpan = new TimeSpan(0, 0);
+  private oldSpan: Span<HighPrecisionTime> = HighPrecisionTimeSpan.ZERO;
   private oldFtraceFilter?: FtraceFilterState;
   private oldPagination?: Pagination;
 
@@ -45,7 +49,7 @@
 
   run() {
     if (this.shouldUpdate()) {
-      this.oldSpan = globals.frontendLocalState.visibleWindowTime.clone();
+      this.oldSpan = globals.frontendLocalState.visibleWindowTime;
       this.oldFtraceFilter = globals.state.ftraceFilter;
       this.oldPagination = globals.state.ftracePagination;
       if (globals.state.ftracePagination.count > 0) {
@@ -66,8 +70,7 @@
   private shouldUpdate(): boolean {
     // Has the visible window moved?
     const visibleWindow = globals.frontendLocalState.visibleWindowTime;
-    if (this.oldSpan.start !== visibleWindow.start ||
-        this.oldSpan.end !== visibleWindow.end) {
+    if (!this.oldSpan.equals(visibleWindow)) {
       return true;
     }
 
@@ -86,11 +89,7 @@
 
   async lookupFtraceEvents(offset: number, count: number): Promise<RetVal> {
     const appState = globals.state;
-    const frontendState = globals.frontendLocalState;
-    const {start, end} = frontendState.visibleWindowTime;
-
-    const startNs = toNsFloor(start);
-    const endNs = toNsCeil(end);
+    const {start, end} = globals.stateVisibleTime();
 
     const excludeList = appState.ftraceFilter.excludedNames;
     const excludeListSql = excludeList.map((s) => `'${s}'`).join(',');
@@ -105,7 +104,7 @@
       from ftrace_event
       where
         ftrace_event.name not in (${excludeListSql}) and
-        ts >= ${startNs} and ts <= ${endNs}
+        ts >= ${start} and ts <= ${end}
       `);
     const {numEvents} = queryRes.firstRow({numEvents: NUM});
 
@@ -125,14 +124,14 @@
       on thread.upid = process.upid
       where
         ftrace_event.name not in (${excludeListSql}) and
-        ts >= ${startNs} and ts <= ${endNs}
+        ts >= ${start} and ts <= ${end}
       order by id
       limit ${count} offset ${offset};`);
     const events: FtraceEvent[] = [];
     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/record_controller_jsdomtest.ts b/ui/src/controller/record_controller_jsdomtest.ts
index f160290..bc7c15b 100644
--- a/ui/src/controller/record_controller_jsdomtest.ts
+++ b/ui/src/controller/record_controller_jsdomtest.ts
@@ -32,7 +32,8 @@
   const result =
       TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'}));
   const sources = assertExists(result.dataSources);
-  const srcConfig = assertExists(sources[0].config);
+  // TODO(hjd): This is all bad. Should just match the whole config.
+  const srcConfig = assertExists(sources[1].config);
   const ftraceConfig = assertExists(srcConfig.ftraceConfig);
   const ftraceEvents = assertExists(ftraceConfig.ftraceEvents);
   expect(ftraceEvents.includes('raw_syscalls/sys_enter')).toBe(true);
@@ -45,7 +46,7 @@
   const result =
       TraceConfig.decode(genConfigProto(config, {os: 'S', name: 'Android S'}));
   const sources = assertExists(result.dataSources);
-  const srcConfig = assertExists(sources[1].config);
+  const srcConfig = assertExists(sources[2].config);
   const ftraceConfig = assertExists(srcConfig.ftraceConfig);
   const ftraceEvents = assertExists(ftraceConfig.ftraceEvents);
   expect(ftraceConfig.symbolizeKsyms).toBe(true);
@@ -58,7 +59,7 @@
   const result =
       TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'}));
   const sources = assertExists(result.dataSources);
-  const srcConfig = assertExists(sources[1].config);
+  const srcConfig = assertExists(sources[2].config);
   const ftraceConfig = assertExists(srcConfig.ftraceConfig);
   const ftraceEvents = assertExists(ftraceConfig.ftraceEvents);
   expect(ftraceConfig.symbolizeKsyms).toBe(false);
@@ -72,7 +73,7 @@
   const result =
       TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'}));
   const sources = assertExists(result.dataSources);
-  const srcConfig = assertExists(sources[0].config);
+  const srcConfig = assertExists(sources[1].config);
   const ftraceConfig = assertExists(srcConfig.ftraceConfig);
   const ftraceEvents = assertExists(ftraceConfig.ftraceEvents);
   expect(ftraceConfig.symbolizeKsyms).toBe(true);
@@ -86,7 +87,7 @@
   const result =
       TraceConfig.decode(genConfigProto(config, {os: 'Q', name: 'Android Q'}));
   const sources = assertExists(result.dataSources);
-  const srcConfig = assertExists(sources[0].config);
+  const srcConfig = assertExists(sources[1].config);
   const ftraceConfig = assertExists(srcConfig.ftraceConfig);
   const ftraceEvents = assertExists(ftraceConfig.ftraceEvents);
   expect(ftraceConfig.symbolizeKsyms).toBe(false);
diff --git a/ui/src/controller/search_controller.ts b/ui/src/controller/search_controller.ts
index d1df094..b2c7e04 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 {LONG, 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,20 +88,19 @@
     // 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) {
       publishSearch({
-        tsStarts: new Float64Array(0),
-        tsEnds: new Float64Array(0),
+        tsStarts: new BigInt64Array(0),
+        tsEnds: new BigInt64Array(0),
         count: new Uint8Array(0),
       });
       publishSearchResult({
         sliceIds: new Float64Array(0),
-        tsStarts: new Float64Array(0),
+        tsStarts: new BigInt64Array(0),
         utids: new Float64Array(0),
         sources: [],
         trackIds: [],
@@ -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.quantFloor(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},
@@ -169,8 +159,8 @@
 
     const res = await this.query(`
         select
-          (quantum_ts * ${quantumNs} + ${startNs})/1e9 as tsStart,
-          ((quantum_ts+1) * ${quantumNs} + ${startNs})/1e9 as tsEnd,
+          (quantum_ts * ${quantumNs} + ${startNs}) as tsStart,
+          ((quantum_ts+1) * ${quantumNs} + ${startNs}) as tsEnd,
           min(count(*), 255) as count
           from (
               select
@@ -187,13 +177,13 @@
           order by quantum_ts;`);
 
     const numRows = res.numRows();
-    const summary = {
-      tsStarts: new Float64Array(numRows),
-      tsEnds: new Float64Array(numRows),
+    const summary: SearchSummary = {
+      tsStarts: new BigInt64Array(numRows),
+      tsEnds: new BigInt64Array(numRows),
       count: new Uint8Array(numRows),
     };
 
-    const it = res.iter({tsStart: NUM, tsEnd: NUM, count: NUM});
+    const it = res.iter({tsStart: LONG, tsEnd: LONG, count: NUM});
     for (let row = 0; it.valid(); it.next(), ++row) {
       summary.tsStarts[row] = it.tsStart;
       summary.tsEnds[row] = it.tsEnd;
@@ -269,7 +259,7 @@
     const rows = queryRes.numRows();
     const searchResults: CurrentSearchResults = {
       sliceIds: new Float64Array(rows),
-      tsStarts: new Float64Array(rows),
+      tsStarts: new BigInt64Array(rows),
       utids: new Float64Array(rows),
       trackIds: [],
       sources: [],
@@ -277,7 +267,7 @@
     };
 
     const it = queryRes.iter(
-        {sliceId: NUM, ts: NUM, source: STR, sourceId: NUM, utid: NUM});
+        {sliceId: NUM, ts: LONG, source: STR, sourceId: NUM, utid: NUM});
     for (; it.valid(); it.next()) {
       let trackId = undefined;
       if (it.source === 'cpu') {
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 b704e87..49301e3 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,35 @@
 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 {
+  defaultTraceTime,
+  EngineMode,
+  PendingDeeplinkState,
+  ProfileType,
+} from '../common/state';
+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 +60,7 @@
 } from '../frontend/globals';
 import {showModal} from '../frontend/modal';
 import {
+  clearOverviewData,
   publishFtraceCounters,
   publishMetricError,
   publishOverviewData,
@@ -415,11 +437,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 =
@@ -452,16 +474,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);
@@ -506,6 +528,12 @@
       await this.selectPerfSample();
     }
 
+    const pendingDeeplink = globals.state.pendingDeeplink;
+    if (pendingDeeplink !== undefined) {
+      globals.dispatch(Actions.clearPendingDeeplink({}));
+      await this.selectPendingDeeplink(pendingDeeplink);
+    }
+
     // If the trace was shared via a permalink, it might already have a
     // selection. Emit onSelectionChanged to ensure that the components (like
     // current selection details) react to it.
@@ -513,6 +541,8 @@
       onSelectionChanged(globals.state.currentSelection, undefined);
     }
 
+    globals.dispatch(Actions.maybeExpandOnlyTrackGroup({}));
+
     // Trace Processor doesn't support the reliable range feature for JSON
     // traces.
     if (!isJsonTrace && ENABLE_CHROME_RELIABLE_RANGE_ANNOTATION_FLAG.get()) {
@@ -539,8 +569,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}));
   }
@@ -559,13 +589,59 @@
       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;
     globals.dispatch(Actions.selectHeapProfile({id: 0, upid, ts, type}));
   }
 
+  private async selectPendingDeeplink(link: PendingDeeplinkState) {
+    const conditions = [];
+    const {ts, dur} = link;
+
+    if (ts !== undefined) {
+      conditions.push(`ts = ${ts}`);
+    }
+    if (dur !== undefined) {
+      conditions.push(`dur = ${dur}`);
+    }
+
+    if (conditions.length === 0) {
+      return;
+    }
+
+    const query = `
+      select
+        id,
+        track_id as traceProcessorTrackId,
+        type
+      from slice
+      where ${conditions.join(' and ')}
+    ;`;
+
+    const result = await assertExists(this.engine).query(query);
+    if (result.numRows() > 0) {
+      const row = result.firstRow({
+        id: NUM,
+        traceProcessorTrackId: NUM,
+        type: STR,
+      });
+
+      const id = row.traceProcessorTrackId;
+      const trackId = globals.state.uiTrackIdByTraceTrackId[id];
+      if (trackId === undefined) {
+        return;
+      }
+      globals.makeSelection(Actions.selectChromeSlice({
+        id: row.id,
+        trackId,
+        table: '',
+        scroll: true,
+      }));
+    }
+  }
+
   private async listTracks() {
     this.updateStatus('Loading tracks');
     const engine = assertExists<Engine>(this.engine);
@@ -609,31 +685,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);
@@ -644,16 +721,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
@@ -664,21 +740,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);
   }
@@ -889,48 +965,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..7934a07 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 {assertExists, assertTrue} from '../base/logging';
+import {BigintMath} from '../base/bigint_math';
+import {assertExists} 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';
@@ -28,10 +34,6 @@
 
 type TrackConfigWithNamespace = TrackConfig&{namespace: string};
 
-// Allow to override via devtools for testing (note, needs to be done in the
-// controller-thread).
-(self as {} as {quantPx: number}).quantPx = 1;
-
 // TrackController is a base class overridden by track implementations (e.g.,
 // sched slices, nestable slices, counters).
 export abstract class TrackController<
@@ -55,10 +57,6 @@
     this.engine = args.engine;
   }
 
-  protected pxSize(): number {
-    return (self as {} as {quantPx: number}).quantPx;
-  }
-
   // Can be overriden by the track implementation to allow one time setup work
   // to be performed before the first onBoundsChange invcation.
   async onSetup() {}
@@ -70,7 +68,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 +127,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 +136,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;
@@ -156,14 +154,13 @@
   // data. Returns the bucket size (in ns) if caching should happen and
   // undefined otherwise.
   // Subclasses should call this in their setup function
-  cachedBucketSizeNs(numRows: number): number|undefined {
+  calcCachedBucketSize(numRows: number): TPDuration|undefined {
     // Ensure that we're not caching when the table size isn't even that big.
     if (numRows < TrackController.MIN_TABLE_SIZE_TO_CACHE) {
       return undefined;
     }
 
-    const bounds = globals.state.traceTime;
-    const traceDurNs = toNs(bounds.endSec - bounds.startSec);
+    const traceDuration = globals.stateTraceTimeTP().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
@@ -193,50 +190,46 @@
 
     // 4k monitors have 3840 horizontal pixels so use that for a worst case
     // approximation of the window width.
-    const approxWidthPx = 3840;
+    const approxWidthPx = 3840n;
 
     // Compute the outermost bucket size. This acts as a starting point for
     // computing the cached size.
-    const outermostResolutionLevel =
-        Math.ceil(Math.log2(traceDurNs / approxWidthPx));
-    const outermostBucketNs = Math.pow(2, outermostResolutionLevel);
+    const outermostBucketSize =
+        BigintMath.bitCeil(traceDuration / approxWidthPx);
+    const outermostResolutionLevel = BigintMath.log2(outermostBucketSize);
 
     // This constant decides how many resolution levels down from our outermost
     // bucket computation we want to be able to use the cached table.
     // We've chosen 7 as it seems to be empircally seems to be a good fit for
     // trace data.
-    const resolutionLevelsCovered = 7;
+    const resolutionLevelsCovered = 7n;
 
     // If we've got less resolution levels in the trace than the number of
     // resolution levels we want to go down, bail out because this cached
     // table is really not going to be used enough.
     if (outermostResolutionLevel < resolutionLevelsCovered) {
-      return Number.MAX_SAFE_INTEGER;
+      return BigintMath.INT64_MAX;
     }
 
     // Another way to look at moving down resolution levels is to consider how
     // many sub-intervals we are splitting the bucket into.
-    const bucketSubIntervals = Math.pow(2, resolutionLevelsCovered);
+    const bucketSubIntervals = 1n << resolutionLevelsCovered;
 
     // Calculate the smallest bucket we want our table to be able to handle by
     // dividing the outermsot bucket by the number of subintervals we should
     // divide by.
-    const cachedBucketSizeNs = outermostBucketNs / bucketSubIntervals;
+    const cachedBucketSize = outermostBucketSize / bucketSubIntervals;
 
-    // Our logic above should make sure this is an integer but double check that
-    // here as an assertion before returning.
-    assertTrue(Number.isInteger(cachedBucketSizeNs));
-
-    return cachedBucketSizeNs;
+    return cachedBucketSize;
   }
 
   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 +246,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 0653552..50b4719 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({
@@ -191,14 +198,42 @@
     return 'Unknown';
   }
 
+  async guessCpuSizes(): Promise<Map<number, string>> {
+    const cpuToSize = new Map<number, string>();
+    await this.engine.query(`
+      SELECT IMPORT('common.cpus');
+    `);
+    const result = await this.engine.query(`
+      SELECT cpu, GUESS_CPU_SIZE(cpu) as size FROM cpu_counter_track;
+    `);
+
+    const it = result.iter({
+      cpu: NUM,
+      size: STR_NULL,
+    });
+
+    for (; it.valid(); it.next()) {
+      const size = it.size;
+      if (size !== null) {
+        cpuToSize.set(it.cpu, size);
+      }
+    }
+
+    return cpuToSize;
+  }
+
   async addCpuSchedulingTracks(): Promise<void> {
     const cpus = await this.engine.getCpus();
+    const cpuToSize = await this.guessCpuSizes();
+
     for (const cpu of cpus) {
+      const size = cpuToSize.get(cpu);
+      const name = size === undefined ? `Cpu ${cpu}` : `Cpu ${cpu} (${size})`;
       this.tracksToAdd.push({
         engineId: this.engineId,
         kind: CPU_SLICE_TRACK_KIND,
         trackSortKey: PrimaryTrackSortKey.ORDINARY_TRACK,
-        name: `Cpu ${cpu}`,
+        name,
         trackGroup: SCROLLING_TRACK_GROUP,
         config: {
           cpu,
@@ -207,6 +242,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();
 
@@ -309,6 +363,7 @@
     });
 
     const parentIdToGroupId = new Map<number, string>();
+    let scrollJankRendered = false;
 
     for (; it.valid(); it.next()) {
       const kind = ASYNC_SLICE_TRACK_KIND;
@@ -353,6 +408,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,
@@ -959,9 +1021,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;
@@ -1276,8 +1338,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/controller/validators.ts b/ui/src/controller/validators.ts
index c35c69d..cc072b7 100644
--- a/ui/src/controller/validators.ts
+++ b/ui/src/controller/validators.ts
@@ -85,6 +85,28 @@
   }
 }
 
+class OptionalValidator<T> implements Validator<T|undefined> {
+  private inner: Validator<T>;
+
+  constructor(inner: Validator<T>) {
+    this.inner = inner;
+  }
+
+  validate(input: unknown, context: ValidatorContext): T|undefined {
+    if (input === undefined) {
+      return undefined;
+    }
+    try {
+      return this.inner.validate(input, context);
+    } catch (e) {
+      if (e instanceof ValidationError) {
+        context.invalidKeys.push(renderPath(context.path));
+        return undefined;
+      }
+      throw e;
+    }
+  }
+}
 
 class StringValidator extends PrimitiveValidator<string> {
   predicate(input: unknown): input is string {
@@ -244,14 +266,25 @@
 
 export const requiredStr = new StringValidator('', true);
 
+export const optStr = new OptionalValidator<string>(requiredStr);
+
 export function num(defaultValue = 0): NumberValidator {
   return new NumberValidator(defaultValue, false);
 }
 
+export const requiredNum = new NumberValidator(0, true);
+
+export const optNum = new OptionalValidator<number>(requiredNum);
+
 export function bool(defaultValue = false): BooleanValidator {
   return new BooleanValidator(defaultValue, false);
 }
 
+export const requiredBool = new BooleanValidator(false, true);
+
+export const optBool = new OptionalValidator<boolean>(requiredBool);
+
+
 export function record<T extends Record<string, Validator<unknown>>>(
     validators: T): RecordValidator<T> {
   return new RecordValidator<T>(validators);
diff --git a/ui/src/controller/validators_unittest.ts b/ui/src/controller/validators_unittest.ts
index eec9aa5..fc0fe51 100644
--- a/ui/src/controller/validators_unittest.ts
+++ b/ui/src/controller/validators_unittest.ts
@@ -16,6 +16,7 @@
   arrayOf,
   num,
   oneOf,
+  optStr,
   record,
   requiredStr,
   runValidator,
@@ -107,3 +108,27 @@
   expect(result.extraKeys).toContain('deeply.nested.array[1].extra3');
   expect(result.invalidKeys).toContain('deeply.nested.array[0].x');
 });
+
+
+describe('optStr', () => {
+  test('it validates undefined', () => {
+    const validation = runValidator(optStr, undefined);
+    expect(validation.result).toEqual(undefined);
+    expect(validation.invalidKeys).toEqual([]);
+    expect(validation.extraKeys).toEqual([]);
+  });
+
+  test('it validates string', () => {
+    const validation = runValidator(optStr, 'foo');
+    expect(validation.result).toEqual('foo');
+    expect(validation.invalidKeys).toEqual([]);
+    expect(validation.extraKeys).toEqual([]);
+  });
+
+  test('it reports numbers', () => {
+    const validation = runValidator(optStr, 42);
+    expect(validation.result).toEqual(undefined);
+    expect(validation.invalidKeys).toEqual(['']);
+    expect(validation.extraKeys).toEqual([]);
+  });
+});
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..88badd1 100644
--- a/ui/src/frontend/base_slice_track.ts
+++ b/ui/src/frontend/base_slice_track.ts
@@ -20,9 +20,14 @@
   colorToStr,
   UNEXPECTED_PINK_COLOR,
 } from '../common/colorizer';
-import {NUM} from '../common/query_result';
+import {LONG, NUM} from '../common/query_result';
 import {Selection, SelectionKind} from '../common/state';
-import {fromNs, toNs} from '../common/time';
+import {
+  TPDuration,
+  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,8 +186,8 @@
   private cache: TrackCache<Array<CastInternal<T['slice']>>> =
       new TrackCache(5);
 
-  private readonly tableName: string;
-  private maxDurNs = 0;
+  protected readonly tableName: string;
+  private maxDurNs: TPDuration = 0n;
   private sqlState: 'UNINITIALIZED'|'INITIALIZING'|'QUERY_PENDING'|
       'QUERY_DONE' = 'UNINITIALIZED';
   private extraSqlColumns: string[];
@@ -272,13 +277,15 @@
   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);
+      const rawStartNs = vizTime.start.toTPTime();
+      const rawEndNs = vizTime.end.toTPTime();
       const rawSlicesKey = CacheKey.create(rawStartNs, rawEndNs, windowSizePx);
 
       // If the visible time range is outside the cached area, requests
@@ -298,7 +305,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 +329,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 +437,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.tpTimeToPx(this.slicesKey.start),
+        timeScale.tpTimeToPx(this.slicesKey.end));
 
     // TODO(hjd): Remove this.
     // The only thing this does is drawing the sched latency arrow. We should
@@ -478,7 +486,7 @@
       const queryRes = await this.engine.query(`select
           ifnull(max(dur), 0) as maxDur, count(1) as rowCount
           from ${this.tableName}`);
-      const row = queryRes.firstRow({maxDur: NUM, rowCount: NUM});
+      const row = queryRes.firstRow({maxDur: LONG, rowCount: NUM});
       this.maxDurNs = row.maxDur;
       this.sqlState = 'QUERY_DONE';
     } else if (
@@ -506,18 +514,18 @@
     }
 
     this.sqlState = 'QUERY_PENDING';
-    const bucketNs = slicesKey.bucketNs;
+    const bucketNs = slicesKey.bucketSize;
     let queryTsq;
     let queryTsqEnd;
     // When we're zoomed into the level of single ns there is no point
     // doing quantization (indeed it causes bad artifacts) so instead
     // we use ts / ts+dur directly.
-    if (bucketNs === 1) {
+    if (bucketNs === 1n) {
       queryTsq = 'ts';
       queryTsqEnd = 'ts + dur';
     } else {
-      queryTsq = `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`;
-      queryTsqEnd = `(ts + dur + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`;
+      queryTsq = `(ts + ${bucketNs / 2n}) / ${bucketNs} * ${bucketNs}`;
+      queryTsqEnd = `(ts + dur + ${bucketNs / 2n}) / ${bucketNs} * ${bucketNs}`;
     }
 
     const extraCols = this.extraSqlColumns.join(',');
@@ -557,8 +565,8 @@
         ${extraCols ? ',' + extraCols : ''}
       from ${this.tableName}
       where
-        ts >= ${slicesKey.startNs - this.maxDurNs /* - durNs */} and
-        ts <= ${slicesKey.endNs /* + durNs */}
+        ts >= ${slicesKey.start - this.maxDurNs /* - durNs */} and
+        ts <= ${slicesKey.end /* + durNs */}
       group by ${maybeGroupByDepth} tsq
       order by tsq),
     q2 as (
@@ -623,8 +631,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 +709,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() {
@@ -785,7 +793,7 @@
     return this.computedTrackHeight;
   }
 
-  getSliceRect(_tStart: number, _tEnd: number, _depth: number): SliceRect
+  getSliceRect(_tStart: TPTime, _tEnd: TPTime, _depth: number): SliceRect
       |undefined {
     // TODO(hjd): Implement this as part of updating flow events.
     return undefined;
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..58efd07 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 {TPDuration, 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) +
@@ -313,7 +351,7 @@
   }
 
   private fillFlowPanel(
-      name: string, flows: {flow: FlowPoint, dur: number}[],
+      name: string, flows: {flow: FlowPoint, dur: TPDuration}[],
       includeProcessName: boolean, result: Map<string, TableBuilder>) {
     if (flows.length === 0) return;
 
@@ -327,7 +365,7 @@
             flow.sliceName :
             flow.sliceChromeCustomName,
       });
-      builder.add('Delay', timeToCode(dur));
+      builder.add('Delay', tpTimeToCode(dur));
       builder.add(
           'Thread',
           includeProcessName ? `${flow.threadName} (${flow.processName})` :
diff --git a/ui/src/frontend/clipboard.ts b/ui/src/frontend/clipboard.ts
index c7a27d7..38d0099 100644
--- a/ui/src/frontend/clipboard.ts
+++ b/ui/src/frontend/clipboard.ts
@@ -43,7 +43,7 @@
     const line = [];
     for (const col of resp.columns) {
       const value = row[col];
-      line.push(value === null ? 'NULL' : value.toString());
+      line.push(value === null ? 'NULL' : `${value}`);
     }
     lines.push(line);
   }
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..ad04665 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';
@@ -36,7 +39,7 @@
 import {globals} from './globals';
 import {LogPanel} from './logs_panel';
 import {NotesEditorTab} from './notes_panel';
-import {AnyAttrsVnode, PanelContainer} from './panel_container';
+import {AnyAttrsVnode} from './panel_container';
 import {PivotTable} from './pivot_table';
 import {SliceDetailsPanel} from './slice_details_panel';
 import {ThreadStateTab} from './thread_state_tab';
@@ -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;
@@ -147,7 +152,7 @@
               onclick: () => {
                 this.isClosed = false;
                 this.isFullscreen = true;
-                this.resize(this.fullscreenHeight);
+                this.resize(this.fullscreenHeight - DRAG_HANDLE_HEIGHT_PX);
                 globals.rafScheduler.scheduleFullRedraw();
               },
               title: 'Open fullscreen',
@@ -167,7 +172,7 @@
                   this.isFullscreen = false;
                   this.isClosed = true;
                   this.previousHeight = this.height;
-                  this.resize(DRAG_HANDLE_HEIGHT_PX);
+                  this.resize(0);
                 }
                 globals.rafScheduler.scheduleFullRedraw();
               },
@@ -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);
   }
@@ -381,27 +390,27 @@
     }
 
     const panel = currentTabDetails?.vnode;
-    const panels = panel ? [panel] : [];
 
-    return m(
-        '.details-content',
-        {
-          style: {
-            height: `${this.detailsHeight}px`,
-            display: detailsPanels.length > 0 ? null : 'none',
-          },
+    if (!panel) {
+      return null;
+    }
+
+    return [
+      m(DragHandle, {
+        resize: (height: number) => {
+          this.detailsHeight = Math.max(height, 0);
         },
-        m(DragHandle, {
-          resize: (height: number) => {
-            this.detailsHeight = Math.max(height, DRAG_HANDLE_HEIGHT_PX);
-          },
-          height: this.detailsHeight,
-          tabs: detailsPanels.map((tab) => {
-            return {key: tab.key, name: tab.name};
-          }),
-          currentTabKey: currentTabDetails?.key,
+        height: this.detailsHeight,
+        tabs: detailsPanels.map((tab) => {
+          return {key: tab.key, name: tab.name};
         }),
-        m('.details-panel-container.x-scrollable',
-          m(PanelContainer, {doesScroll: true, panels, kind: 'DETAILS'})));
+        currentTabKey: currentTabDetails?.key,
+      }),
+      m('.details-panel-container',
+        {
+          style: {height: `${this.detailsHeight}px`},
+        },
+        panel),
+    ];
   }
 }
diff --git a/ui/src/frontend/drag/border_drag_strategy.ts b/ui/src/frontend/drag/border_drag_strategy.ts
index df450fc..e046293 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.gt(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_panel.ts b/ui/src/frontend/flow_events_panel.ts
index 755c75d..ee2f146 100644
--- a/ui/src/frontend/flow_events_panel.ts
+++ b/ui/src/frontend/flow_events_panel.ts
@@ -15,7 +15,7 @@
 import m from 'mithril';
 
 import {Actions} from '../common/actions';
-import {timeToCode} from '../common/time';
+import {tpTimeToCode} from '../common/time';
 
 import {Flow, globals} from './globals';
 import {BLANK_CHECKBOX, CHECKBOX} from './icons';
@@ -95,7 +95,7 @@
 
       const data = [
         m('td.flow-link', args, outgoing ? 'Outgoing' : 'Incoming'),
-        m('td.flow-link', args, timeToCode(flow.dur)),
+        m('td.flow-link', args, tpTimeToCode(flow.dur)),
         m('td.flow-link', args, otherEnd.sliceId.toString()),
         m('td.flow-link', args, otherEnd.sliceName),
         m('td.flow-link', args, flow.begin.threadName),
diff --git a/ui/src/frontend/flow_events_renderer.ts b/ui/src/frontend/flow_events_renderer.ts
index 909dcb7..c511758 100644
--- a/ui/src/frontend/flow_events_renderer.ts
+++ b/ui/src/frontend/flow_events_renderer.ts
@@ -12,6 +12,7 @@
 // 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 {ALL_CATEGORIES, getFlowCategories} from './flow_events_panel';
 import {Flow, FlowPoint, globals} from './globals';
@@ -139,8 +140,8 @@
     };
   }
 
-  private getXCoordinate(ts: number): number {
-    return globals.frontendLocalState.timeScale.timeToPx(ts);
+  private getXCoordinate(ts: TPTime): number {
+    return globals.frontendLocalState.visibleTimeScale.tpTimeToPx(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 34663d8..bfbd130 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, TPDuration} 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,97 @@
   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) {
+    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, TPDuration> {
+    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.lt(traceTimeSpan.start)) {
+      this._start = traceTimeSpan.start;
+    }
+
+    const end = this._start.addNanos(this._durationNanos);
+    if (end.gt(traceTimeSpan.end)) {
+      this._start = traceTimeSpan.end.subNanos(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 +170,9 @@
 
   private _visibleState: VisibleState = {
     lastUpdate: 0,
-    startSec: 0,
-    endSec: 10,
-    resolution: 1,
+    start: 0n,
+    end: BigInt(10e9),
+    resolution: 1n,
   };
 
   private _selectedArea?: Area;
@@ -125,6 +213,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,19 +235,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,
-        `Impossible select area: startSec [${startSec}] >= endSec [${endSec}]`);
+        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();
   }
 
@@ -166,12 +267,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();
   }
 
@@ -181,17 +281,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
@@ -202,7 +302,33 @@
     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;
+  }
+
+  // Get the visible time span as
+  get visibleTimeSpan(): Span<TPTime, TPDuration> {
+    return this.visibleWindow.timestampSpan;
+  }
 }
diff --git a/ui/src/frontend/ftrace_panel.ts b/ui/src/frontend/ftrace_panel.ts
index 8de83cb..f66cddf 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';
@@ -68,7 +68,7 @@
   }
 
   private scrollContainer(dom: Element): HTMLElement {
-    const el = dom.parentElement!.parentElement!.parentElement;
+    const el = dom.parentElement;
     return assertExists(el);
   }
 
@@ -117,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() {
@@ -188,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;
 
@@ -204,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..1518987 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;
@@ -75,8 +86,8 @@
   sliceName: string;
   sliceCategory: string;
   sliceId: number;
-  sliceStartTs: number;
-  sliceEndTs: number;
+  sliceStartTs: TPTime;
+  sliceEndTs: TPTime;
   // Thread and process info. Only set in sliceSelected not in areaSelected as
   // the latter doesn't display per-flow info and it'd be a waste to join
   // additional tables for undisplayed info in that case. Nothing precludes
@@ -97,30 +108,30 @@
 
   begin: FlowPoint;
   end: FlowPoint;
-  dur: number;
+  dur: TPDuration;
 
   category?: string;
   name?: string;
 }
 
 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[];
@@ -137,14 +148,14 @@
 
 export interface CpuProfileDetails {
   id?: number;
-  ts?: number;
+  ts?: TPTime;
   utid?: number;
   stack?: CallsiteInfo[];
 }
 
 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;
@@ -244,15 +255,15 @@
 
   private _currentSearchResults: CurrentSearchResults = {
     sliceIds: new Float64Array(0),
-    tsStarts: new Float64Array(0),
+    tsStarts: new BigInt64Array(0),
     utids: new Float64Array(0),
     trackIds: [],
     sources: [],
     totalResults: 0,
   };
   searchSummary: SearchSummary = {
-    tsStarts: new Float64Array(0),
-    tsEnds: new Float64Array(0),
+    tsStarts: new BigInt64Array(0),
+    tsEnds: new BigInt64Array(0),
     count: new Uint8Array(0),
   };
 
@@ -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,16 @@
     // 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 = timeScale.pxDeltaToDuration(this.quantPx);
+
+    return BigintMath.bitFloor(timePerPx.toTPTime('floor'));
   }
 
   getCurrentEngine(): EngineConfig|undefined {
@@ -600,7 +603,7 @@
     this._metricResult = undefined;
     this._currentSearchResults = {
       sliceIds: new Float64Array(0),
-      tsStarts: new Float64Array(0),
+      tsStarts: new BigInt64Array(0),
       utids: new Float64Array(0),
       trackIds: [],
       sources: [],
@@ -637,6 +640,41 @@
   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 TimeScale.fromHPTimeSpan(traceTime, 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);
+  }
+
+  // How many pixels to use for one quanta of horizontal resolution
+  get quantPx(): number {
+    const quantPx = (self as {} as {quantPx: number | undefined}).quantPx;
+    if (quantPx) {
+      return quantPx;
+    } else {
+      // Default to 1px per quanta if not defined
+      return 1;
+    }
+  }
 }
 
 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/index.ts b/ui/src/frontend/index.ts
index cef61e8..053316f 100644
--- a/ui/src/frontend/index.ts
+++ b/ui/src/frontend/index.ts
@@ -142,12 +142,6 @@
   }));
 }
 
-function initGlobalsFromQueryString() {
-  const queryString = window.location.search;
-  globals.embeddedMode = queryString.includes('mode=embedded');
-  globals.hideSidebar = queryString.includes('hideSidebar=true');
-}
-
 function setupContentSecurityPolicy() {
   // Note: self and sha-xxx must be quoted, urls data: and blob: must not.
   const policy = {
@@ -252,9 +246,10 @@
     maybeOpenTraceFromRoute(route);
   };
 
-  // This must be called before calling `globals.initialize` so that the
-  // `embeddedMode` global is set.
-  initGlobalsFromQueryString();
+  // These need to be set before globals.initialize.
+  const route = Router.parseUrl(window.location.href);
+  globals.embeddedMode = route.args.mode === 'embedded';
+  globals.hideSidebar = route.args.hideSidebar === true;
 
   globals.initialize(dispatch, router);
   globals.serviceWorkerController.install();
@@ -311,7 +306,6 @@
   }
 }
 
-
 function onCssLoaded() {
   initCssConstants();
   // Clear all the contents of the initial page (e.g. the <pre> error message)
@@ -343,6 +337,14 @@
   // accidentially clober the state of an open trace processor instance
   // otherwise.
   CheckHttpRpcConnection().then(() => {
+    const route = Router.parseUrl(window.location.href);
+
+    globals.dispatch(Actions.maybeSetPendingDeeplink({
+      ts: route.args.ts,
+      tid: route.args.tid,
+      dur: route.args.dur,
+    }));
+
     if (!globals.embeddedMode) {
       installFileDropHandler();
     }
@@ -359,7 +361,7 @@
 
     // Handles the initial ?local_cache_key=123 or ?s=permalink or ?url=...
     // cases.
-    maybeOpenTraceFromRoute(Router.parseUrl(window.location.href));
+    maybeOpenTraceFromRoute(route);
   });
 }
 
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..8c50589 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';
@@ -58,16 +58,19 @@
   }
 
   oncreate({dom}: m.CVnodeDOM) {
-    this.scrollContainer = assertExists(
-        dom.parentElement!.parentElement!.parentElement as HTMLElement);
+    this.scrollContainer = assertExists(dom.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 +82,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 +95,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 +151,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..dc8a45d 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,10 @@
   // 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 = TimeScale.fromHPTimeSpan(traceTime, pxSpan);
     if (this.gesture === undefined) {
       this.gesture = new DragGestureHandler(
           dom as HTMLElement,
@@ -78,26 +80,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 +117,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 +213,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.ts b/ui/src/frontend/pivot_table.ts
index 530868f..bf56ad2 100644
--- a/ui/src/frontend/pivot_table.ts
+++ b/ui/src/frontend/pivot_table.ts
@@ -27,7 +27,7 @@
   PivotTableResult,
   SortDirection,
 } from '../common/state';
-import {fromNs, timeToCode} from '../common/time';
+import {tpTimeToCode} from '../common/time';
 
 import {globals} from './globals';
 import {Panel} from './panel';
@@ -198,8 +198,8 @@
   renderCell(column: TableColumn, value: ColumnType): string {
     if (column.kind === 'regular' &&
         (column.column === 'dur' || column.column === 'thread_dur')) {
-      if (typeof value === 'number') {
-        return timeToCode(fromNs(value));
+      if (typeof value === 'bigint') {
+        return tpTimeToCode(value);
       }
     }
     return `${value}`;
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..be864fc 100644
--- a/ui/src/frontend/query_table.ts
+++ b/ui/src/frontend/query_table.ts
@@ -14,22 +14,19 @@
 
 
 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 {Row} from '../common/query_result';
 
+import {Anchor} from './anchor';
 import {copyToClipboard, queryResponseToClipboard} from './clipboard';
 import {downloadData} from './download_utils';
 import {globals} from './globals';
 import {Panel} from './panel';
 import {Router} from './router';
-import {
-  focusHorizontalRange,
-  verticalScrollToTrack,
-} from './scroll_helper';
+import {reveal} from './scroll_helper';
 import {Button} from './widgets/button';
 
 interface QueryTableRowAttrs {
@@ -37,98 +34,126 @@
   columns: string[];
 }
 
-// Convert column value to number if it's a bigint or a number, otherwise throw
-function colToNumber(colValue: ColumnType): number {
-  if (typeof colValue === 'bigint') {
-    return Number(colValue);
-  } else if (typeof colValue === 'number') {
-    return colValue;
+type Numeric = bigint|number;
+
+function isIntegral(x: Row[string]): x is Numeric {
+  return typeof x === 'bigint' ||
+      (typeof x === 'number' && Number.isInteger(x));
+}
+
+function hasTs(row: Row): row is Row&{ts: Numeric} {
+  return ('ts' in row && isIntegral(row.ts));
+}
+
+function hasDur(row: Row): row is Row&{dur: Numeric} {
+  return ('dur' in row && isIntegral(row.dur));
+}
+
+function hasTrackId(row: Row): row is Row&{track_id: Numeric} {
+  return ('track_id' in row && isIntegral(row.track_id));
+}
+
+function hasType(row: Row): row is Row&{type: string} {
+  return ('type' in row && typeof row.type === 'string');
+}
+
+function hasId(row: Row): row is Row&{id: Numeric} {
+  return ('id' in row && isIntegral(row.id));
+}
+
+function hasSliceId(row: Row): row is Row&{slice_id: Numeric} {
+  return ('slice_id' in row && isIntegral(row.slice_id));
+}
+
+// These are properties that a row should have in order to be "slice-like",
+// insofar as it represents a time range and a track id which can be revealed
+// or zoomed-into on the timeline.
+type Sliceish = {
+  ts: Numeric,
+  dur: Numeric,
+  track_id: Numeric
+};
+
+export function isSliceish(row: Row): row is Row&Sliceish {
+  return hasTs(row) && hasDur(row) && hasTrackId(row);
+}
+
+// Attempts to extract a slice ID from a row, or undefined if none can be found
+export function getSliceId(row: Row): number|undefined {
+  if (hasType(row) && row.type.includes('slice')) {
+    if (hasId(row)) {
+      return Number(row.id);
+    }
   } else {
-    throw Error('Value is not a number or a bigint');
+    if (hasSliceId(row)) {
+      return Number(row.slice_id);
+    }
   }
+  return undefined;
 }
 
 class QueryTableRow implements m.ClassComponent<QueryTableRowAttrs> {
-  static columnsContainsSliceLocation(columns: string[]) {
-    const requiredColumns = ['ts', 'dur', 'track_id'];
-    for (const col of requiredColumns) {
-      if (!columns.includes(col)) return false;
-    }
-    return true;
-  }
-
-  static rowOnClickHandler(
-      event: Event, row: Row, nextTab: 'CurrentSelection'|'QueryResults') {
-    // TODO(dproy): Make click handler work from analyze page.
-    if (Router.parseUrl(window.location.href).page !== '/viewer') return;
-    // If the click bubbles up to the pan and zoom handler that will deselect
-    // the slice.
-    event.stopPropagation();
-
-    const sliceStart = fromNs(colToNumber(row.ts));
-    // row.dur can be negative. Clamp to 1ns.
-    const sliceDur = fromNs(Math.max(colToNumber(row.dur), 1));
-    const sliceEnd = sliceStart + sliceDur;
-    const trackId: number = 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;
-    if (row.type?.toString().includes('slice')) {
-      sliceId = colToNumber(row.id);
-    } else {
-      sliceId = colToNumber(row.slice_id);
-    }
-    if (sliceId !== undefined) {
-      globals.makeSelection(
-          Actions.selectChromeSlice(
-              {id: sliceId, trackId: uiTrackId, table: 'slice'}),
-          nextTab === 'QueryResults' ? globals.state.currentTab :
-                                       'current_selection');
-    }
-  }
-
   view(vnode: m.Vnode<QueryTableRowAttrs>) {
-    const cells = [];
     const {row, columns} = vnode.attrs;
-    for (const col of columns) {
-      const value = row[col];
-      if (value instanceof Uint8Array) {
-        cells.push(
-            m('td',
-              m(Anchor,
-                {
-                  onclick: () => downloadData(`${col}.blob`, value),
-                },
-                `Blob (${value.length} bytes)`)));
-      } else if (typeof value === 'bigint') {
-        cells.push(m('td', value.toString()));
-      } else {
-        cells.push(m('td', value));
+    const cells = columns.map((col) => this.renderCell(col, row[col]));
+
+    // TODO(dproy): Make click handler work from analyze page.
+    if (Router.parseUrl(window.location.href).page === '/viewer' &&
+        isSliceish(row)) {
+      return m(
+          'tr',
+          {
+            onclick: () => this.highlightSlice(row, globals.state.currentTab),
+            // TODO(altimin): Consider improving the logic here (e.g. delay?) to
+            // account for cases when dblclick fires late.
+            ondblclick: () => this.highlightSlice(row),
+            clickable: true,
+          },
+          cells);
+    } else {
+      return m('tr', cells);
+    }
+  }
+
+  private renderCell(name: string, value: Row[string]) {
+    if (value instanceof Uint8Array) {
+      return m('td', this.renderBlob(name, value));
+    } else {
+      return m('td', `${value}`);
+    }
+  }
+
+  private renderBlob(name: string, value: Uint8Array) {
+    return m(
+        Anchor,
+        {
+          onclick: () => downloadData(`${name}.blob`, value),
+        },
+        `Blob (${value.length} bytes)`);
+  }
+
+  private highlightSlice(row: Row&Sliceish, nextTab?: string) {
+    const trackId = Number(row.track_id);
+    const sliceStart = BigInt(row.ts);
+    // row.dur can be negative. Clamp to 1ns.
+    const sliceDur = BigintMath.max(BigInt(row.dur), 1n);
+    const uiTrackId = globals.state.uiTrackIdByTraceTrackId[trackId];
+    if (uiTrackId !== undefined) {
+      reveal(uiTrackId, sliceStart, sliceStart + sliceDur, true);
+      const sliceId = getSliceId(row);
+      if (sliceId !== undefined) {
+        this.selectSlice(sliceId, uiTrackId, nextTab);
       }
     }
-    const containsSliceLocation =
-        QueryTableRow.columnsContainsSliceLocation(columns);
-    const maybeOnClick = containsSliceLocation ?
-        (e: Event) => QueryTableRow.rowOnClickHandler(e, row, 'QueryResults') :
-        null;
-    const maybeOnDblClick = containsSliceLocation ?
-        (e: Event) =>
-            QueryTableRow.rowOnClickHandler(e, row, 'CurrentSelection') :
-        null;
-    return m(
-        'tr',
-        {
-          'onclick': maybeOnClick,
-          // TODO(altimin): Consider improving the logic here (e.g. delay?) to
-          // account for cases when dblclick fires late.
-          'ondblclick': maybeOnDblClick,
-          'clickable': containsSliceLocation,
-        },
-        cells);
+  }
+
+  private selectSlice(sliceId: number, uiTrackId: string, nextTab?: string) {
+    const action = Actions.selectChromeSlice({
+      id: sliceId,
+      trackId: uiTrackId,
+      table: 'slice',
+    });
+    globals.makeSelection(action, nextTab);
   }
 }
 
@@ -158,9 +183,7 @@
     if (resp.error) {
       return m('.query-error', `SQL error: ${resp.error}`);
     } else {
-      return m(
-          '.query-table-container.x-scrollable',
-          m('table.query-table', m('thead', tableHeader), m('tbody', rows)));
+      return m('table.query-table', m('thead', tableHeader), m('tbody', rows));
     }
   }
 }
@@ -211,7 +234,7 @@
     const headers = [m('header.overview', ...header)];
 
     if (resp === undefined) {
-      return m('div', ...headers);
+      return headers;
     }
 
     if (resp.statementWithOutputCount > 1) {
@@ -222,7 +245,7 @@
                 `statement are displayed in the table below.`));
     }
 
-    return m('div', ...headers, m(QueryTableContent, {resp}));
+    return [...headers, m(QueryTableContent, {resp})];
   }
 
   renderCanvas() {}
diff --git a/ui/src/frontend/query_table_unittest.ts b/ui/src/frontend/query_table_unittest.ts
new file mode 100644
index 0000000..fc351a6
--- /dev/null
+++ b/ui/src/frontend/query_table_unittest.ts
@@ -0,0 +1,46 @@
+// 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 {getSliceId, isSliceish} from './query_table';
+
+describe('getSliceId', () => {
+  test('get slice_id if present when no other clues are available', () => {
+    expect(getSliceId({})).toBe(undefined);
+    expect(getSliceId({id: 123})).toBe(undefined);
+    expect(getSliceId({slice_id: 456})).toBe(456);
+    expect(getSliceId({id: 123, slice_id: 456})).toBe(456);
+
+    expect(getSliceId({type: 'foo'})).toBe(undefined);
+    expect(getSliceId({type: 'foo', id: 123})).toBe(undefined);
+    expect(getSliceId({type: 'foo', slice_id: 456})).toBe(456);
+    expect(getSliceId({type: 'foo', id: 123, slice_id: 456})).toBe(456);
+  });
+
+  test('get id if present when row looks like a slice', () => {
+    expect(getSliceId({type: 'slice'})).toBe(undefined);
+    expect(getSliceId({type: 'slice', id: 123})).toBe(123);
+    expect(getSliceId({type: 'slice', slice_id: 456})).toBe(undefined);
+    expect(getSliceId({type: 'slice', id: 123, slice_id: 456})).toBe(123);
+  });
+});
+
+test('isSliceish', () => {
+  expect(isSliceish({})).toBeFalsy();
+  expect(isSliceish({ts: 123, dur: 456})).toBeFalsy();
+  expect(isSliceish({ts: 123, dur: 456, track_id: 798})).toBeTruthy();
+  expect(isSliceish({ts: 123n, dur: 456n})).toBeFalsy();
+  expect(isSliceish({ts: 123n, dur: 456n, track_id: 798n})).toBeTruthy();
+  expect(isSliceish({ts: 123.4, dur: 456.7, track_id: 798.9})).toBeFalsy();
+  expect(isSliceish({ts: '123', dur: '456', track_id: '789'})).toBeFalsy();
+});
diff --git a/ui/src/frontend/router.ts b/ui/src/frontend/router.ts
index a693b2e..1057d80 100644
--- a/ui/src/frontend/router.ts
+++ b/ui/src/frontend/router.ts
@@ -13,20 +13,24 @@
 // limitations under the License.
 
 import m from 'mithril';
+
 import {assertExists, assertTrue} from '../base/logging';
+import {
+  oneOf,
+  optBool,
+  optStr,
+  record,
+  runValidator,
+  ValidatedType,
+} from '../controller/validators';
+
 import {PageAttrs} from './pages';
 
 export const ROUTE_PREFIX = '#!';
 const DEFAULT_ROUTE = '/';
 
-// A broken down representation of a route.
-// For instance: #!/record/gpu?local_cache_key=a0b1
-// becomes: {page: '/record', subpage: '/gpu', args: {local_cache_key: 'a0b1'}}
-export interface Route {
-  page: string;
-  subpage: string;
-  args: RouteArgs;
-}
+const modes = ['embedded', undefined] as const;
+type Mode = typeof modes[number];
 
 // The set of args that can be set on the route via #!/page?a=1&b2.
 // Route args are orthogonal to pages (i.e. should NOT make sense only in a
@@ -44,18 +48,46 @@
 //   This is client-only. All the routing logic in the Perfetto UI uses only
 //   this.
 
-// This must be a type literial to avoid having to duplicate the
-// index type logic of Params.
-export type RouteArgs = {
+const routeArgs = record({
   // The local_cache_key is special and is persisted across navigations.
-  local_cache_key?: string;
+  local_cache_key: optStr,
 
   // These are transient and are really set only on startup.
-  openFromAndroidBugTool?: string;
-  s?: string;    // For permalinks.
-  p?: string;    // DEPRECATED: for #!/record?p=cpu subpages (b/191255021).
-  url?: string;  // For fetching traces from Cloud Storage.
-};
+
+  // Are we loading a trace via ABT.
+  openFromAndroidBugTool: optBool,
+
+  // For permalink hash.
+  s: optStr,
+
+  // DEPRECATED: for #!/record?p=cpu subpages (b/191255021).
+  p: optStr,
+
+  // For fetching traces from Cloud Storage.
+  url: optStr,
+
+  // For the 'mode' of the UI. For example when the mode is 'embedded'
+  // some features are disabled.
+  mode: oneOf<Mode>(modes, undefined),
+
+  // Should we hide the sidebar?
+  hideSidebar: optBool,
+
+  // Deep link support
+  ts: optStr,
+  dur: optStr,
+  tid: optStr,
+});
+type RouteArgs = ValidatedType<typeof routeArgs>;
+
+// A broken down representation of a route.
+// For instance: #!/record/gpu?local_cache_key=a0b1
+// becomes: {page: '/record', subpage: '/gpu', args: {local_cache_key: 'a0b1'}}
+export interface Route {
+  page: string;
+  subpage: string;
+  args: RouteArgs;
+}
 
 export interface RoutesMap {
   [key: string]: m.Component<PageAttrs>;
@@ -168,16 +200,50 @@
 
     const argsStart = hash.indexOf('?');
     const argsStr = argsStart < 0 ? '' : hash.substring(argsStart + 1);
-    const args = argsStr ? m.parseQueryString(hash.substring(argsStart)) : {};
+    const rawArgs =
+        argsStr ? m.parseQueryString(hash.substring(argsStart)) : {};
+
+    const args = runValidator(routeArgs, rawArgs).result;
+
+    // Javascript sadly distinguishes between foo[bar] === undefined
+    // and foo[bar] is not set at all. Here we need the second case to
+    // avoid making the URL ugly.
+    for (const key of Object.keys(args)) {
+      if ((args as any)[key] === undefined) {
+        delete (args as any)[key];
+      }
+    }
 
     return {page, subpage, args};
   }
 
+  private static parseSearchParams(url: string): RouteArgs {
+    const query = (new URL(url)).search;
+    const rawArgs = m.parseQueryString(query);
+    const args = runValidator(routeArgs, rawArgs).result;
+
+    // Javascript sadly distinguishes between foo[bar] === undefined
+    // and foo[bar] is not set at all. Here we need the second case to
+    // avoid making the URL ugly.
+    for (const key of Object.keys(args)) {
+      if ((args as any)[key] === undefined) {
+        delete (args as any)[key];
+      }
+    }
+
+    return args;
+  }
+
   // Like parseFragment() but takes a full URL.
   static parseUrl(url: string): Route {
+    const searchArgs = Router.parseSearchParams(url);
+
     const hashPos = url.indexOf('#');
     const fragment = hashPos < 0 ? '' : url.substring(hashPos);
-    return Router.parseFragment(fragment);
+    const route = Router.parseFragment(fragment);
+    route.args = Object.assign({}, searchArgs, route.args);
+
+    return route;
   }
 
   // Throws if EVENT_LIMIT onhashchange events occur within WINDOW_MS.
diff --git a/ui/src/frontend/router_unittest.ts b/ui/src/frontend/router_unittest.ts
index 612347c..19dac36 100644
--- a/ui/src/frontend/router_unittest.ts
+++ b/ui/src/frontend/router_unittest.ts
@@ -18,89 +18,138 @@
   view() {},
 };
 
-beforeEach(() => {
-  window.location.hash = '';
-});
-
-test('Default route must be defined', () => {
-  expect(() => new Router({'/a': mockComponent})).toThrow();
-});
-
-test('Resolves empty route to default component', () => {
-  const router = new Router({'/': mockComponent});
-  window.location.hash = '';
-  expect(router.resolve().tag).toBe(mockComponent);
-});
-
-test('Resolves subpage route to component of main page', () => {
-  const nonDefaultComponent = {view() {}};
-  const router = new Router({
-    '/': mockComponent,
-    '/a': nonDefaultComponent,
+describe('Router#resolve', () => {
+  beforeEach(() => {
+    window.location.hash = '';
   });
-  window.location.hash = '#!/a/subpage';
-  expect(router.resolve().tag).toBe(nonDefaultComponent);
-  expect(router.resolve().attrs.subpage).toBe('/subpage');
-});
 
-test('Pass empty subpage if not found in URL', () => {
-  const nonDefaultComponent = {view() {}};
-  const router = new Router({
-    '/': mockComponent,
-    '/a': nonDefaultComponent,
+  test('Default route must be defined', () => {
+    expect(() => new Router({'/a': mockComponent})).toThrow();
   });
-  window.location.hash = '#!/a';
-  expect(router.resolve().tag).toBe(nonDefaultComponent);
-  expect(router.resolve().attrs.subpage).toBe('');
+
+  test('Resolves empty route to default component', () => {
+    const router = new Router({'/': mockComponent});
+    window.location.hash = '';
+    expect(router.resolve().tag).toBe(mockComponent);
+  });
+
+  test('Resolves subpage route to component of main page', () => {
+    const nonDefaultComponent = {view() {}};
+    const router = new Router({
+      '/': mockComponent,
+      '/a': nonDefaultComponent,
+    });
+    window.location.hash = '#!/a/subpage';
+    expect(router.resolve().tag).toBe(nonDefaultComponent);
+    expect(router.resolve().attrs.subpage).toBe('/subpage');
+  });
+
+  test('Pass empty subpage if not found in URL', () => {
+    const nonDefaultComponent = {view() {}};
+    const router = new Router({
+      '/': mockComponent,
+      '/a': nonDefaultComponent,
+    });
+    window.location.hash = '#!/a';
+    expect(router.resolve().tag).toBe(nonDefaultComponent);
+    expect(router.resolve().attrs.subpage).toBe('');
+  });
 });
 
-test('Args parsing', () => {
-  const url = 'http://localhost/#!/foo?p=123&s=42&url=a?b?c';
-  const args = Router.parseUrl(url).args;
-  expect(args.p).toBe('123');
-  expect(args.s).toBe('42');
-  expect(args.url).toBe('a?b?c');
+describe('Router.parseUrl', () => {
+  // Can parse arguments from the search string.
+  test('Search parsing', () => {
+    const url = 'http://localhost?p=123&s=42&url=a?b?c';
+    const args = Router.parseUrl(url).args;
+    expect(args.p).toBe('123');
+    expect(args.s).toBe('42');
+    expect(args.url).toBe('a?b?c');
+  });
+
+  // Or from the fragment string.
+  test('Fragment parsing', () => {
+    const url = 'http://localhost/#!/foo?p=123&s=42&url=a?b?c';
+    const args = Router.parseUrl(url).args;
+    expect(args.p).toBe('123');
+    expect(args.s).toBe('42');
+    expect(args.url).toBe('a?b?c');
+  });
+
+  // Or both in which case fragment overrides the search.
+  test('Fragment parsing', () => {
+    const url =
+        'http://localhost/?p=1&s=2&hideSidebar=true#!/foo?s=3&url=4&hideSidebar=false';
+    const args = Router.parseUrl(url).args;
+    expect(args.p).toBe('1');
+    expect(args.s).toBe('3');
+    expect(args.url).toBe('4');
+    expect(args.hideSidebar).toBe(false);
+  });
 });
 
-test('empty route broken into empty components', () => {
-  const {page, subpage, args} = Router.parseFragment('');
-  expect(page).toBe('');
-  expect(subpage).toBe('');
-  expect(args).toEqual({});
-});
+describe('Router.parseFragment', () => {
+  test('empty route broken into empty components', () => {
+    const {page, subpage, args} = Router.parseFragment('');
+    expect(page).toBe('');
+    expect(subpage).toBe('');
+    expect(args.mode).toBe(undefined);
+  });
 
-test('invalid route broken into empty components', () => {
-  const {page, subpage, args} = Router.parseFragment('/bla');
-  expect(page).toBe('');
-  expect(subpage).toBe('');
-  expect(args).toEqual({});
-});
+  test('by default args are undefined', () => {
+    // This prevents the url from becoming messy.
+    const {args} = Router.parseFragment('');
+    expect(args).toEqual({});
+  });
 
-test('simple route has page defined', () => {
-  const {page, subpage, args} = Router.parseFragment('#!/record');
-  expect(page).toBe('/record');
-  expect(subpage).toBe('');
-  expect(args).toEqual({});
-});
+  test('invalid route broken into empty components', () => {
+    const {page, subpage} = Router.parseFragment('/bla');
+    expect(page).toBe('');
+    expect(subpage).toBe('');
+  });
 
-test('simple route has both components defined', () => {
-  const {page, subpage, args} = Router.parseFragment('#!/record/memory');
-  expect(page).toBe('/record');
-  expect(subpage).toBe('/memory');
-  expect(args).toEqual({});
-});
+  test('simple route has page defined', () => {
+    const {page, subpage} = Router.parseFragment('#!/record');
+    expect(page).toBe('/record');
+    expect(subpage).toBe('');
+  });
 
-test('route broken at first slash', () => {
-  const {page, subpage, args} = Router.parseFragment('#!/record/memory/stuff');
-  expect(page).toBe('/record');
-  expect(subpage).toBe('/memory/stuff');
-  expect(args).toEqual({});
-});
+  test('simple route has both components defined', () => {
+    const {page, subpage} = Router.parseFragment('#!/record/memory');
+    expect(page).toBe('/record');
+    expect(subpage).toBe('/memory');
+  });
 
-test('parameters separated from route', () => {
-  const {page, subpage, args} =
-      Router.parseFragment('#!/record/memory?url=http://localhost:1234/aaaa');
-  expect(page).toBe('/record');
-  expect(subpage).toBe('/memory');
-  expect(args).toEqual({url: 'http://localhost:1234/aaaa'});
+  test('route broken at first slash', () => {
+    const {page, subpage} = Router.parseFragment('#!/record/memory/stuff');
+    expect(page).toBe('/record');
+    expect(subpage).toBe('/memory/stuff');
+  });
+
+  test('parameters separated from route', () => {
+    const {page, subpage, args} =
+        Router.parseFragment('#!/record/memory?url=http://localhost:1234/aaaa');
+    expect(page).toBe('/record');
+    expect(subpage).toBe('/memory');
+    expect(args.url).toEqual('http://localhost:1234/aaaa');
+  });
+
+  test('openFromAndroidBugTool can be false', () => {
+    const {args} = Router.parseFragment('#!/?openFromAndroidBugTool=false');
+    expect(args.openFromAndroidBugTool).toEqual(false);
+  });
+
+  test('openFromAndroidBugTool can be true', () => {
+    const {args} = Router.parseFragment('#!/?openFromAndroidBugTool=true');
+    expect(args.openFromAndroidBugTool).toEqual(true);
+  });
+
+  test('bad modes are coerced to default', () => {
+    const {args} = Router.parseFragment('#!/?mode=1234');
+    expect(args.mode).toEqual(undefined);
+  });
+
+  test('bad hideSidebar is coerced to default', () => {
+    const {args} = Router.parseFragment('#!/?hideSidebar=helloworld!');
+    expect(args.hideSidebar).toEqual(undefined);
+  });
 });
diff --git a/ui/src/frontend/scroll_helper.ts b/ui/src/frontend/scroll_helper.ts
index 18a9a78..387913f 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) {
+export function horizontalScrollToTs(ts: TPTime) {
+  const time = HighPrecisionTime.fromTPTime(ts);
+  const visibleWindow = globals.frontendLocalState.visibleWindowTime;
+  if (!visibleWindow.contains(time)) {
     // 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)));
+    const halfDuration = visibleWindow.duration.divide(2);
+    const newStart = time.sub(halfDuration);
+    const newWindow = new HighPrecisionTimeSpan(
+        newStart, newStart.add(visibleWindow.duration));
+    globals.frontendLocalState.updateVisibleTime(newWindow);
   }
 }
 
@@ -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.gt(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.sub(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.gt(trace.end)) {
+    newStart = trace.end.sub(visible.duration);
+    newEnd = trace.end;
   }
-  if (newStartTs < globals.state.traceTime.startSec) {
-    newStartTs = globals.state.traceTime.startSec;
-    newEndTs = globals.state.traceTime.startSec + visibleDur;
+  if (newStart.lt(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.eq(visible.start) && view.end.eq(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,9 +147,16 @@
 
 // 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);
   }
   horizontalScrollToTs(ts);
 }
+
+// Scroll vertically and horizontally to a track and time range
+export function reveal(
+    trackId: string|number, start: TPTime, end: TPTime, openGroup = false) {
+  verticalScrollToTrack(trackId, openGroup);
+  focusHorizontalRange(start, end);
+}
diff --git a/ui/src/frontend/search_handler.ts b/ui/src/frontend/search_handler.ts
index 1622a7e..190dab9 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.stateTraceTimeTP();
+  const startNs = vizWindow.start;
+  const endNs = vizWindow.end;
   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..6076188 100644
--- a/ui/src/frontend/tickmark_panel.ts
+++ b/ui/src/frontend/tickmark_panel.ts
@@ -14,11 +14,12 @@
 
 import m from 'mithril';
 
-import {fromNs} from '../common/time';
+import {TPTimeSpan} from '../common/time';
 
 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 {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 visibleSpan = globals.frontendLocalState.visibleWindow.timestampSpan;
+    if (size.width > TRACK_SHELL_WIDTH && visibleSpan.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(
+               visibleSpan, 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) {
+      const segmentSpan = new TPTimeSpan(tStart, tEnd);
+      if (!visibleSpan.intersects(segmentSpan)) {
         continue;
       }
       const rectStart =
-          Math.max(timeScale.timeToPx(tStart), 0) + TRACK_SHELL_WIDTH;
-      const rectEnd = timeScale.timeToPx(tEnd) + TRACK_SHELL_WIDTH;
+          Math.max(visibleTimeScale.tpTimeToPx(tStart), 0) + TRACK_SHELL_WIDTH;
+      const rectEnd = visibleTimeScale.tpTimeToPx(tEnd) + TRACK_SHELL_WIDTH;
       ctx.fillStyle = '#ffe263';
       ctx.fillRect(
           Math.floor(rectStart),
@@ -61,16 +75,20 @@
           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 && index <= globals.currentSearchResults.tsStarts.length) {
+      const start = globals.currentSearchResults.tsStarts[index];
+      const triangleStart =
+          Math.max(visibleTimeScale.tpTimeToPx(start), 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..6d31a99 100644
--- a/ui/src/frontend/time_scale.ts
+++ b/ui/src/frontend/time_scale.ts
@@ -12,95 +12,93 @@
 // 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();
+  static fromHPTimeSpan(span: Span<HighPrecisionTime>, pxSpan: PxSpan) {
+    return new TimeScale(span.start, span.duration.nanos, pxSpan);
   }
 
-  private updateSlope() {
-    this.secPerPx = this.timeBounds.duration / (this._endPx - this._startPx);
+  constructor(start: HighPrecisionTime, durationNanos: number, pxSpan: PxSpan) {
+    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;
   }
 
-  deltaTimeToPx(time: number): number {
-    return Math.round(time / this.secPerPx);
+  get timeSpan(): Span<HighPrecisionTime> {
+    const end = this._start.addNanos(this._durationNanos);
+    return new HighPrecisionTimeSpan(this._start, end);
   }
 
-  timeToPx(time: number): number {
-    return this._startPx + (time - this.timeBounds.start) / 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;
   }
 
-  pxToTime(px: number): number {
-    return this.timeBounds.start + (px - this._startPx) * this.secPerPx;
+  secondsToPx(seconds: number): number {
+    const timeOffset = (seconds - this._startSec) * 1e9;
+    return this.pxSpan.start + timeOffset / this._nanosPerPx;
   }
 
-  deltaPxToDuration(px: number): number {
-    return px * this.secPerPx;
+  hpTimeToPx(time: HighPrecisionTime): number {
+    const timeOffsetNanos = time.sub(this._start).nanos;
+    return this.pxSpan.start + timeOffsetNanos / this._nanosPerPx;
   }
 
-  setTimeBounds(timeBounds: TimeSpan) {
-    this.timeBounds = timeBounds;
-    this.updateSlope();
+  // 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);
   }
 
-  setLimitsPx(pxStart: number, pxEnd: number) {
-    assertFalse(pxStart === pxEnd);
-    assertTrue(pxStart >= 0 && pxEnd >= 0);
-    this._startPx = pxStart;
-    this._endPx = pxEnd;
-    this.updateSlope();
+  durationToPx(dur: TPDuration): number {
+    // WARNING: Number(bigint) can be surprisingly slow. Avoid in hotpath.
+    return Number(dur) / this._nanosPerPx;
   }
 
-  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/trace_url_handler.ts b/ui/src/frontend/trace_url_handler.ts
index 8b398fb..5e329f5 100644
--- a/ui/src/frontend/trace_url_handler.ts
+++ b/ui/src/frontend/trace_url_handler.ts
@@ -23,7 +23,6 @@
 import {Route, Router} from './router';
 import {taskTracker} from './task_tracker';
 
-
 export function maybeOpenTraceFromRoute(route: Route) {
   if (route.args.s) {
     // /?s=xxxx for permalinks.
diff --git a/ui/src/frontend/track.ts b/ui/src/frontend/track.ts
index 2d5ab81..9b59059 100644
--- a/ui/src/frontend/track.ts
+++ b/ui/src/frontend/track.ts
@@ -17,6 +17,7 @@
 import {assertExists} from '../base/logging';
 import {Engine} from '../common/engine';
 import {TrackState} from '../common/state';
+import {TPTime} from '../common/time';
 import {TrackData} from '../common/track_data';
 
 import {checkerboard} from './checkerboard';
@@ -130,9 +131,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 +178,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;
     }
@@ -205,7 +208,7 @@
   // only for track types that support slices e.g. chrome_slice, async_slices
   // tStart - slice start time in seconds, tEnd - slice end time in seconds,
   // depth - slice depth
-  getSliceRect(_tStart: number, _tEnd: number, _depth: number): SliceRect
+  getSliceRect(_tStart: TPTime, _tEnd: TPTime, _depth: number): SliceRect
       |undefined {
     return undefined;
   }
diff --git a/ui/src/frontend/track_cache.ts b/ui/src/frontend/track_cache.ts
index 049cbf4..61f17b1 100644
--- a/ui/src/frontend/track_cache.ts
+++ b/ui/src/frontend/track_cache.ts
@@ -12,7 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../base/bigint_math';
+
 import {assertTrue} from '../base/logging';
+import {TPDuration, TPTime} from '../common/time';
 
 export const BUCKETS_PER_PIXEL = 2;
 
@@ -53,32 +56,34 @@
 // non-normal window at a higher resolution. Normalization is used to
 // avoid re-fetching data on tiny zooms/moves/resizes.
 export class CacheKey {
-  readonly startNs: number;
-  readonly endNs: number;
-  readonly bucketNs: number;
+  readonly start: TPTime;
+  readonly end: TPTime;
+  readonly bucketSize: TPDuration;
   readonly windowSizePx: number;
 
-  static create(startNs: number, endNs: number, windowSizePx: number):
+  static create(startNs: TPTime, endNs: TPTime, windowSizePx: number):
       CacheKey {
-    const bucketNs = (endNs - startNs) / (windowSizePx * BUCKETS_PER_PIXEL);
+    const bucketNs = (endNs - startNs) /
+        BigInt(Math.round(windowSizePx * BUCKETS_PER_PIXEL));
     return new CacheKey(startNs, endNs, bucketNs, windowSizePx);
   }
 
   private constructor(
-      startNs: number, endNs: number, bucketNs: number, windowSizePx: number) {
-    this.startNs = startNs;
-    this.endNs = endNs;
-    this.bucketNs = bucketNs;
+      startNs: TPTime, endNs: TPTime, bucketNs: TPDuration,
+      windowSizePx: number) {
+    this.start = startNs;
+    this.end = endNs;
+    this.bucketSize = bucketNs;
     this.windowSizePx = windowSizePx;
   }
 
   static zero(): CacheKey {
-    return new CacheKey(0, 0, 0, 100);
+    return new CacheKey(0n, 0n, 0n, 100);
   }
 
-  get normalizedBucketNs(): number {
+  get normalizedBucketNs(): bigint {
     // Round bucketNs down to the nearest smaller power of 2 (minimum 1):
-    return Math.max(1, Math.pow(2, Math.floor(Math.log2(this.bucketNs))));
+    return BigintMath.max(1n, BigintMath.bitFloor(this.bucketSize));
   }
 
   get normalizedWindowSizePx(): number {
@@ -88,9 +93,9 @@
   normalize(): CacheKey {
     const windowSizePx = this.normalizedWindowSizePx;
     const bucketNs = this.normalizedBucketNs;
-    const windowNs = windowSizePx * BUCKETS_PER_PIXEL * bucketNs;
-    const startNs = Math.floor(this.startNs / windowNs) * windowNs;
-    const endNs = Math.ceil(this.endNs / windowNs) * windowNs;
+    const windowNs = BigInt(windowSizePx * BUCKETS_PER_PIXEL) * bucketNs;
+    const startNs = BigintMath.quantFloor(this.start, windowNs);
+    const endNs = BigintMath.quantCeil(this.end, windowNs);
     return new CacheKey(startNs, endNs, bucketNs, windowSizePx);
   }
 
@@ -100,8 +105,8 @@
 
   isCoveredBy(other: CacheKey): boolean {
     let r = true;
-    r = r && other.startNs <= this.startNs;
-    r = r && other.endNs >= this.endNs;
+    r = r && other.start <= this.start;
+    r = r && other.end >= this.end;
     r = r && other.normalizedBucketNs === this.normalizedBucketNs;
     r = r && other.normalizedWindowSizePx === this.normalizedWindowSizePx;
     return r;
@@ -110,9 +115,9 @@
   // toString is 'load bearing' in that it's used to key e.g. caches
   // with CacheKey's.
   toString() {
-    const start = this.startNs;
-    const end = this.endNs;
-    const bucket = this.bucketNs;
+    const start = this.start;
+    const end = this.end;
+    const bucket = this.bucketSize;
     const size = this.windowSizePx;
     return `CacheKey<${start}, ${end}, ${bucket}, ${size}>`;
   }
diff --git a/ui/src/frontend/track_cache_unittest.ts b/ui/src/frontend/track_cache_unittest.ts
index 546406a..fc39a7a 100644
--- a/ui/src/frontend/track_cache_unittest.ts
+++ b/ui/src/frontend/track_cache_unittest.ts
@@ -15,46 +15,46 @@
 import {CacheKey, TrackCache} from './track_cache';
 
 test('cacheKeys', () => {
-  const k = CacheKey.create(201, 302, 123);
+  const k = CacheKey.create(201n, 302n, 123);
   const n = k.normalize();
   const n2 = n.normalize();
   expect(k.isNormalized()).toEqual(false);
   expect(n.isNormalized()).toEqual(true);
   expect(n2.isNormalized()).toEqual(true);
   expect(n).toEqual(n2);
-  expect(n.startNs).toBeLessThanOrEqual(k.startNs);
-  expect(n.endNs).toBeGreaterThanOrEqual(k.startNs);
-  expect(n.bucketNs).toBeGreaterThanOrEqual(k.bucketNs);
+  expect(n.start).toBeLessThanOrEqual(k.start);
+  expect(n.end).toBeGreaterThanOrEqual(k.start);
+  expect(n.bucketSize).toBeGreaterThanOrEqual(k.bucketSize);
   expect(Math.abs(n.windowSizePx - k.windowSizePx)).toBeLessThanOrEqual(200);
 });
 
 test('cache', () => {
-  const k1 = (CacheKey.create(1000, 1100, 100)).normalize();
-  const k2 = (CacheKey.create(2000, 2100, 100)).normalize();
-  const k3 = (CacheKey.create(3000, 3100, 100)).normalize();
-  const k4 = (CacheKey.create(4000, 4100, 100)).normalize();
-  const k5 = (CacheKey.create(5000, 5100, 100)).normalize();
-  const k6 = (CacheKey.create(6000, 6100, 100)).normalize();
-  const k7 = (CacheKey.create(7000, 7100, 100)).normalize();
+  const key1 = (CacheKey.create(1000n, 1100n, 100)).normalize();
+  const key2 = (CacheKey.create(2000n, 2100n, 100)).normalize();
+  const key3 = (CacheKey.create(3000n, 3100n, 100)).normalize();
+  const key4 = (CacheKey.create(4000n, 4100n, 100)).normalize();
+  const key5 = (CacheKey.create(5000n, 5100n, 100)).normalize();
+  const key6 = (CacheKey.create(6000n, 6100n, 100)).normalize();
+  const key7 = (CacheKey.create(7000n, 7100n, 100)).normalize();
   const cache = new TrackCache<string>(5);
 
-  cache.insert(k1, 'v1');
-  expect(cache.lookup(k1)).toEqual('v1');
+  cache.insert(key1, 'v1');
+  expect(cache.lookup(key1)).toEqual('v1');
 
-  cache.insert(k2, 'v2');
-  cache.insert(k3, 'v3');
-  cache.insert(k4, 'v4');
-  cache.insert(k5, 'v5');
+  cache.insert(key2, 'v2');
+  cache.insert(key3, 'v3');
+  cache.insert(key4, 'v4');
+  cache.insert(key5, 'v5');
 
-  // Should push k1/v1 out of the cache:
-  cache.insert(k6, 'v6');
-  expect(cache.lookup(k1)).toEqual(undefined);
+  // Should push key1/v1 out of the cache:
+  cache.insert(key6, 'v6');
+  expect(cache.lookup(key1)).toEqual(undefined);
 
-  // Access k2 then add one more entry:
-  expect(cache.lookup(k2)).toEqual('v2');
-  cache.insert(k7, 'v7');
+  // Access key2 then add one more entry:
+  expect(cache.lookup(key2)).toEqual('v2');
+  cache.insert(key7, 'v7');
 
-  // k2/v2 should still be present but k3/v3 should be discarded:
-  expect(cache.lookup(k2)).toEqual('v2');
-  expect(cache.lookup(k3)).toEqual(undefined);
+  // key2/v2 should still be present but key3/v3 should be discarded:
+  expect(cache.lookup(key2)).toEqual('v2');
+  expect(cache.lookup(key3)).toEqual(undefined);
 });
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 1ab998f..b03d18a 100644
--- a/ui/src/frontend/track_panel.ts
+++ b/ui/src/frontend/track_panel.ts
@@ -17,6 +17,7 @@
 
 import {Actions} from '../common/actions';
 import {TrackState} from '../common/state';
+import {TPTime} from '../common/time';
 
 import {SELECTION_FILL_COLOR, TRACK_SHELL_WIDTH} from './css_constants';
 import {PerfettoMouseEvent} from './events';
@@ -95,7 +96,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),
@@ -145,12 +145,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;
@@ -158,7 +152,6 @@
     globals.rafScheduler.scheduleFullRedraw();
     dataTransfer.setData('perfetto/track', `${this.attrs!.trackState.id}`);
     dataTransfer.setDragImage(new Image(), 0, 0);
-    e.stopImmediatePropagation();
   }
 
   ondragend() {
@@ -370,20 +363,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);
     }
   }
@@ -404,20 +397,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`);
@@ -428,7 +421,7 @@
           globals.sliceDetails.wakeupTs !== undefined) {
         drawVerticalLineAtTime(
             ctx,
-            localState.timeScale,
+            visibleTimeScale,
             globals.sliceDetails.wakeupTs,
             size.height,
             `black`);
@@ -442,26 +435,26 @@
             '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);
       }
     }
   }
 
-  getSliceRect(tStart: number, tDur: number, depth: number): SliceRect
+  getSliceRect(tStart: TPTime, tDur: TPTime, depth: number): SliceRect
       |undefined {
     if (this.track === undefined) {
       return undefined;
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 d3651e7..0f8414c 100644
--- a/ui/src/frontend/viewer_page.ts
+++ b/ui/src/frontend/viewer_page.ts
@@ -13,10 +13,10 @@
 // limitations under the License.
 
 import m from 'mithril';
+import {BigintMath} from '../base/bigint_math';
 
 import {Actions} from '../common/actions';
 import {featureFlags} from '../common/feature_flags';
-import {TimeSpan} from '../common/time';
 
 import {TRACK_SHELL_WIDTH} from './css_constants';
 import {DetailsPanel} from './details_panel';
@@ -28,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';
@@ -53,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);
@@ -119,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();
@@ -141,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) => {
@@ -159,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;
@@ -167,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;
         }
diff --git a/ui/src/frontend/widgets/button.ts b/ui/src/frontend/widgets/button.ts
index 6c90c2e..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
@@ -37,6 +38,9 @@
   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;
 }
@@ -66,6 +70,7 @@
       disabled = false,
       rightIcon,
       className,
+      dismissPopup = false,
       ...htmlAttrs
     } = attrs;
 
@@ -75,6 +80,7 @@
         compact && 'pf-compact',
         minimal && 'pf-minimal',
         (icon && !label) && 'pf-icon-only',
+        dismissPopup && Popup.DISMISS_POPUP_GROUP_CLASS,
         className,
     );
 
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/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..e797343 100644
--- a/ui/src/frontend/widgets/tree.ts
+++ b/ui/src/frontend/widgets/tree.ts
@@ -1,7 +1,10 @@
 import m from 'mithril';
+
 import {classNames} from '../classnames';
 import {globals} from '../globals';
+
 import {Button} from './button';
+import {Spinner} from './spinner';
 import {hasChildren} from './utils';
 
 export enum TreeLayout {
@@ -48,12 +51,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.
+  left?: m.Children;
+  // Content to display in the right hand column.
   // If omitted, this side will be left blank.
-  right?: m.Child;
+  right?: m.Children;
+  // 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.Children;
   // Whether this node is collapsed or not.
   // If omitted, collapsed state 'uncontrolled' - i.e. controlled internally.
   collapsed?: boolean;
@@ -86,8 +93,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.isCollapsed(vnode)) {
+      return m('.pf-tree-right', summary ?? right);
+    } else {
+      return m('.pf-tree-right', right);
+    }
   }
 
   private renderChildren(vnode: m.CVnode<TreeNodeAttrs>) {
@@ -126,3 +138,81 @@
     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);
+}
+
+interface LazyTreeNodeAttrs {
+  // Same as TreeNode (see above).
+  left?: m.Children;
+  // Same as TreeNode (see above).
+  right?: m.Children;
+  // Same as TreeNode (see above).
+  summary?: m.Children;
+  // A callback to be called when the TreeNode is expanded, in order to fetch
+  // child nodes.
+  // The callback must return a promise to a function which returns m.Children.
+  // The reason the promise must return a function rather than the actual
+  // children is to avoid storing vnodes between render cycles, which is a bug
+  // in Mithril.
+  fetchData: () => Promise<() => m.Children>;
+  // Whether to keep child nodes in memory after the node has been collapsed.
+  // Defaults to true
+  hoardData?: boolean;
+}
+
+// This component is a TreeNode which only loads child nodes when it's expanded.
+// This allows us to represent huge trees without having to load all the data
+// up front, and even allows us to represent infinite or recursive trees.
+export class LazyTreeNode implements m.ClassComponent<LazyTreeNodeAttrs> {
+  private collapsed: boolean = true;
+  private renderChildren = this.renderSpinner;
+
+  private renderSpinner(): m.Children {
+    return m(TreeNode, {left: m(Spinner)});
+  }
+
+  view({attrs}: m.CVnode<LazyTreeNodeAttrs>): m.Children {
+    const {
+      left,
+      right,
+      summary,
+      fetchData,
+      hoardData = true,
+    } = attrs;
+
+    return m(
+        TreeNode,
+        {
+          left,
+          right,
+          summary,
+          collapsed: this.collapsed,
+          onCollapseChanged: (collapsed) => {
+            if (collapsed) {
+              if (!hoardData) {
+                this.renderChildren = this.renderSpinner;
+              }
+            } else {
+              fetchData().then((result) => {
+                if (!this.collapsed) {
+                  this.renderChildren = result;
+                  globals.rafScheduler.scheduleFullRedraw();
+                }
+              });
+            }
+            this.collapsed = collapsed;
+            globals.rafScheduler.scheduleFullRedraw();
+          },
+        },
+        this.renderChildren());
+  }
+}
diff --git a/ui/src/frontend/widgets_page.ts b/ui/src/frontend/widgets_page.ts
index 8b7b2e3..989be89 100644
--- a/ui/src/frontend/widgets_page.ts
+++ b/ui/src/frontend/widgets_page.ts
@@ -34,7 +34,7 @@
 import {Spinner} from './widgets/spinner';
 import {Switch} from './widgets/switch';
 import {TextInput} from './widgets/text_input';
-import {Tree, TreeLayout, TreeNode} from './widgets/tree';
+import {LazyTreeNode, Tree, TreeLayout, TreeNode} from './widgets/tree';
 
 const options: {[key: string]: boolean} = {
   foobar: false,
@@ -243,6 +243,18 @@
   }
 }
 
+function recursiveLazyTreeNode(
+    left: string, summary: string, hoardData: boolean): m.Children {
+  return m(LazyTreeNode, {
+    left,
+    summary,
+    hoardData,
+    fetchData: async () => {
+      await new Promise((r) => setTimeout(r, 200));
+      return () => recursiveLazyTreeNode(left, summary, hoardData);
+    },
+  });
+}
 
 export const WidgetsPage = createPage({
   view() {
@@ -563,9 +575,14 @@
                 left: 'Process',
                 right: m(Anchor, {text: '/bin/foo[789]', icon: 'open_in_new'}),
               }),
+              recursiveLazyTreeNode('Lazy', '(hoarding)', true),
+              recursiveLazyTreeNode('Lazy', '(non-hoarding)', false),
               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(
@@ -604,6 +621,27 @@
                 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..d01abc1 100644
--- a/ui/src/tracks/actual_frames/index.ts
+++ b/ui/src/tracks/actual_frames/index.ts
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath as BIMath} from '../../base/bigint_math';
 import {PluginContext} from '../../common/plugin_api';
-import {NUM, NUM_NULL, STR} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {LONG, LONG_NULL, NUM, STR} from '../../common/query_result';
+import {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {TrackController} from '../../controller/track_controller';
 import {NewTrackArgs, Track} from '../../frontend/track';
@@ -31,8 +32,8 @@
   // Slices are stored in a columnar fashion. All fields have the same length.
   strings: string[];
   sliceIds: Float64Array;
-  starts: Float64Array;
-  ends: Float64Array;
+  starts: BigInt64Array;
+  ends: BigInt64Array;
   depths: Uint16Array;
   titles: Uint16Array;   // Index in |strings|.
   colors?: Uint16Array;  // Index in |strings|.
@@ -49,20 +50,11 @@
 
 class ActualFramesSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = ACTUAL_FRAMES_SLICE_TRACK_KIND;
-  private maxDurNs = 0;
+  private maxDur = 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);
-
-    if (this.maxDurNs === 0) {
+    if (this.maxDur === 0n) {
       const maxDurResult = await this.query(`
         select
           max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur))
@@ -70,12 +62,12 @@
         from experimental_slice_layout
         where filter_track_ids = '${this.config.trackIds.join(',')}'
       `);
-      this.maxDurNs = maxDurResult.firstRow({maxDur: NUM_NULL}).maxDur || 0;
+      this.maxDur = maxDurResult.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
     }
 
     const rawResult = await this.query(`
       SELECT
-        (s.ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+        (s.ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
         s.ts as ts,
         max(iif(s.dur = -1, (SELECT end_ts FROM trace_bounds) - s.ts, s.dur))
             as dur,
@@ -97,8 +89,8 @@
       join actual_frame_timeline_slice afs using(id)
       where
         filter_track_ids = '${this.config.trackIds.join(',')}' and
-        s.ts >= ${startNs - this.maxDurNs} and
-        s.ts <= ${endNs}
+        s.ts >= ${start - this.maxDur} and
+        s.ts <= ${end}
       group by tsq, s.layout_depth
       order by tsq, s.layout_depth
     `);
@@ -111,8 +103,8 @@
       length: numRows,
       strings: [],
       sliceIds: new Float64Array(numRows),
-      starts: new Float64Array(numRows),
-      ends: new Float64Array(numRows),
+      starts: new BigInt64Array(numRows),
+      ends: new BigInt64Array(numRows),
       depths: new Uint16Array(numRows),
       titles: new Uint16Array(numRows),
       colors: new Uint16Array(numRows),
@@ -131,9 +123,9 @@
     }
 
     const it = rawResult.iter({
-      'tsq': NUM,
-      'ts': NUM,
-      'dur': NUM,
+      'tsq': LONG,
+      'ts': LONG,
+      'dur': LONG,
       'layoutDepth': NUM,
       'id': NUM,
       'name': STR,
@@ -142,16 +134,15 @@
       'color': STR,
     });
     for (let i = 0; it.valid(); i++, it.next()) {
-      const startNsQ = it.tsq;
-      const startNs = it.ts;
-      const durNs = it.dur;
-      const endNs = startNs + durNs;
+      const startQ = it.tsq;
+      const start = it.ts;
+      const dur = it.dur;
+      const end = start + dur;
+      const minEnd = startQ + resolution;
+      const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
 
-      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
-      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
-
-      slices.starts[i] = fromNs(startNsQ);
-      slices.ends[i] = fromNs(endNsQ);
+      slices.starts[i] = startQ;
+      slices.ends[i] = endQ;
       slices.depths[i] = it.layoutDepth;
       slices.titles[i] = internString(it.name);
       slices.colors![i] = internString(it.color);
diff --git a/ui/src/tracks/android_log/index.ts b/ui/src/tracks/android_log/index.ts
index b2a824b..30d6913 100644
--- a/ui/src/tracks/android_log/index.ts
+++ b/ui/src/tracks/android_log/index.ts
@@ -13,8 +13,8 @@
 // limitations under the License.
 
 import {PluginContext} from '../../common/plugin_api';
-import {NUM} from '../../common/query_result';
-import {fromNs, toNsCeil, toNsFloor} from '../../common/time';
+import {LONG, NUM} from '../../common/query_result';
+import {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {LIMIT} from '../../common/track_data';
 import {
@@ -31,8 +31,7 @@
   numEvents: number;
 
   // Below: data quantized by resolution and aggregated by event priority.
-
-  timestamps: Float64Array;
+  timestamps: BigInt64Array;
 
   // Each Uint8 value has the i-th bit is set if there is at least one log
   // event at the i-th priority level at the corresponding time in |timestamps|.
@@ -61,21 +60,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};`);
 
@@ -86,14 +79,13 @@
       resolution,
       length: rowCount,
       numEvents: 0,
-      timestamps: new Float64Array(rowCount),
+      timestamps: new BigInt64Array(rowCount),
       priorities: new Uint8Array(rowCount),
     };
 
-
-    const it = queryRes.iter({tsQuant: NUM, prio: NUM, numEvents: NUM});
+    const it = queryRes.iter({tsQuant: LONG, prio: NUM, numEvents: NUM});
     for (let row = 0; it.valid(); it.next(), row++) {
-      result.timestamps[row] = fromNs(it.tsQuant);
+      result.timestamps[row] = it.tsQuant;
       const prio = Math.min(it.prio, 7);
       result.priorities[row] |= (1 << prio);
       result.numEvents += it.numEvents;
@@ -113,16 +105,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 +125,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 +135,7 @@
         }
         if (!hasEventsForCurColor) continue;
         ctx.fillStyle = LEVELS[lev].color;
-        const px = Math.floor(timeScale.timeToPx(data.timestamps[i]));
+        const px = Math.floor(visibleTimeScale.tpTimeToPx(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..c84e2b9 100644
--- a/ui/src/tracks/async_slices/index.ts
+++ b/ui/src/tracks/async_slices/index.ts
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath as BIMath} from '../../base/bigint_math';
 import {PluginContext} from '../../common/plugin_api';
-import {NUM, NUM_NULL, STR} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {LONG, LONG_NULL, NUM, STR} from '../../common/query_result';
+import {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -33,8 +34,8 @@
   // Slices are stored in a columnar fashion. All fields have the same length.
   strings: string[];
   sliceIds: Float64Array;
-  starts: Float64Array;
-  ends: Float64Array;
+  starts: BigInt64Array;
+  ends: BigInt64Array;
   depths: Uint16Array;
   titles: Uint16Array;  // Index in |strings|.
   isInstant: Uint16Array;
@@ -43,31 +44,22 @@
 
 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);
-
-    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(`
       SELECT
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+      (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
         ts,
         max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
         layout_depth as depth,
@@ -78,8 +70,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
     `);
@@ -92,8 +84,8 @@
       length: numRows,
       strings: [],
       sliceIds: new Float64Array(numRows),
-      starts: new Float64Array(numRows),
-      ends: new Float64Array(numRows),
+      starts: new BigInt64Array(numRows),
+      ends: new BigInt64Array(numRows),
       depths: new Uint16Array(numRows),
       titles: new Uint16Array(numRows),
       isInstant: new Uint16Array(numRows),
@@ -111,9 +103,9 @@
     }
 
     const it = queryRes.iter({
-      tsq: NUM,
-      ts: NUM,
-      dur: NUM,
+      tsq: LONG,
+      ts: LONG,
+      dur: LONG,
       depth: NUM,
       name: STR,
       id: NUM,
@@ -121,16 +113,15 @@
       isIncomplete: NUM,
     });
     for (let row = 0; it.valid(); it.next(), row++) {
-      const startNsQ = it.tsq;
-      const startNs = it.ts;
-      const durNs = it.dur;
-      const endNs = startNs + durNs;
+      const startQ = it.tsq;
+      const start = it.ts;
+      const dur = it.dur;
+      const end = start + dur;
+      const minEnd = startQ + resolution;
+      const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
 
-      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
-      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
-
-      slices.starts[row] = fromNs(startNsQ);
-      slices.ends[row] = fromNs(endNsQ);
+      slices.starts[row] = startQ;
+      slices.ends[row] = endQ;
       slices.depths[row] = it.depth;
       slices.titles[row] = internString(it.name);
       slices.sliceIds[row] = it.id;
diff --git a/ui/src/tracks/chrome_slices/index.ts b/ui/src/tracks/chrome_slices/index.ts
index 23f9a5e..4e190f9 100644
--- a/ui/src/tracks/chrome_slices/index.ts
+++ b/ui/src/tracks/chrome_slices/index.ts
@@ -12,16 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath as BIMath} from '../../base/bigint_math';
 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, LONG_NULL, NUM, STR} from '../../common/query_result';
+import {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
-import {
-  TrackController,
-} from '../../controller/track_controller';
+import {TrackController} from '../../controller/track_controller';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
 import {cachedHsluvToHex} from '../../frontend/hsluv_cache';
@@ -35,8 +35,6 @@
 const INNER_CHEVRON_OFFSET = -3;
 const INNER_CHEVRON_SCALE =
     (SLICE_HEIGHT - 2 * INNER_CHEVRON_OFFSET) / SLICE_HEIGHT;
-// the lowest bucketNs gets is 2, but add some room in case of fp error
-const MIN_QUANT_NS = 3;
 
 export interface Config {
   maxDepth: number;
@@ -48,8 +46,8 @@
   // Slices are stored in a columnar fashion.
   strings: string[];
   sliceIds: Float64Array;
-  starts: Float64Array;
-  ends: Float64Array;
+  starts: BigInt64Array;
+  ends: BigInt64Array;
   depths: Uint16Array;
   titles: Uint16Array;   // Index into strings.
   colors?: Uint16Array;  // Index into strings.
@@ -60,39 +58,23 @@
 
 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 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;
-    }
-
-    // Buckets are always even and positive, don't quantize once we zoom to
-    // nanosecond-scale, so that we can see exact sizes.
-    let tsq = `ts`;
-    if (bucketNs > MIN_QUANT_NS) {
-      tsq = `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`;
+      this.maxDurNs = queryRes.firstRow({maxDur: LONG_NULL}).maxDur || 0n;
     }
 
     const query = `
       SELECT
-        ${tsq} as tsq,
+        (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
         ts,
         max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
         depth,
@@ -103,8 +85,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);
 
@@ -116,8 +98,8 @@
       length: numRows,
       strings: [],
       sliceIds: new Float64Array(numRows),
-      starts: new Float64Array(numRows),
-      ends: new Float64Array(numRows),
+      starts: new BigInt64Array(numRows),
+      ends: new BigInt64Array(numRows),
       depths: new Uint16Array(numRows),
       titles: new Uint16Array(numRows),
       isInstant: new Uint16Array(numRows),
@@ -136,52 +118,39 @@
     }
 
     const it = queryRes.iter({
-      tsq: NUM,
-      ts: NUM,
-      dur: NUM,
+      tsq: LONG,
+      ts: LONG,
+      dur: LONG,
       depth: NUM,
       sliceId: NUM,
       name: STR,
       isInstant: NUM,
       isIncomplete: NUM,
-      threadDur: NUM_NULL,
+      threadDur: LONG_NULL,
     });
     for (let row = 0; it.valid(); it.next(), row++) {
-      const startNsQ = it.tsq;
-      const startNs = it.ts;
-      const durNs = it.dur;
-      const endNs = startNs + durNs;
+      const startQ = it.tsq;
+      const start = it.ts;
+      const dur = it.dur;
+      const end = start + dur;
+      const minEnd = startQ + resolution;
+      const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
 
-      let endNsQ = endNs;
-      if (bucketNs > MIN_QUANT_NS) {
-        endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
-        endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
-      }
-
-      let isInstant = it.isInstant;
-      // Floating point rounding with large numbers of nanoseconds can mean
-      // there isn't enough precision to distinguish the start and end of a very
-      // short event so we just display the event as an instant when zoomed in
-      // rather than fail completely if the start and end time are the same.
-      if (startNsQ === endNsQ) {
-        isInstant = 1;
-      }
-
-      slices.starts[row] = fromNs(startNsQ);
-      slices.ends[row] = fromNs(endNsQ);
+      slices.starts[row] = startQ;
+      slices.ends[row] = endQ;
       slices.depths[row] = it.depth;
       slices.sliceIds[row] = it.sliceId;
       slices.titles[row] = internString(it.name);
-      slices.isInstant[row] = isInstant;
+      slices.isInstant[row] = it.isInstant;
       slices.isIncomplete[row] = it.isIncomplete;
 
       let cpuTimeRatio = 1;
-      if (!isInstant && !it.isIncomplete && it.threadDur !== null) {
+      if (!it.isInstant && !it.isIncomplete && it.threadDur !== null) {
         // Rounding the CPU time ratio to two decimal places and ensuring
         // it is less than or equal to one, incase the thread duration exceeds
         // the total duration.
-        cpuTimeRatio =
-            Math.min(Math.round((it.threadDur / it.dur) * 100) / 100, 1);
+        cpuTimeRatio = Math.min(
+            Math.round(BIMath.ratio(it.threadDur, it.dur) * 100) / 100, 1);
       }
       slices.cpuTimeRatio![row] = cpuTimeRatio;
     }
@@ -209,7 +178,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 +188,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 +214,9 @@
       const title = data.strings[titleId];
       const colorOverride = data.colors && data.strings[data.colors[i]];
       if (isIncomplete) {  // incomplete slice
-        tEnd = visibleWindowTime.end;
+        // TODO(stevegolton): This isn't exactly equivalent, ideally we should
+        // choose tEnd once we've conerted to screen space coords.
+        tEnd = visibleWindowTime.end.toTPTime('ceil');
       }
 
       const rect = this.getSliceRect(tStart, tEnd, depth);
@@ -369,26 +340,30 @@
   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 t = timeScale.pxToHpTime(x);
     const depth = Math.floor((y - TRACK_PADDING) / SLICE_HEIGHT);
+
     for (let i = 0; i < data.starts.length; i++) {
       if (depth !== data.depths[i]) {
         continue;
       }
-      const tStart = data.starts[i];
+      const tStart = HighPrecisionTime.fromTPTime(data.starts[i]);
       if (data.isInstant[i]) {
-        if (Math.abs(tStart - t) < instantWidthTime) {
+        if (tStart.sub(t).abs().lt(instantWidthTime)) {
           return i;
         }
       } else {
-        let tEnd = data.ends[i];
+        let tEnd = HighPrecisionTime.fromTPTime(data.ends[i]);
         if (data.isIncomplete[i]) {
-          tEnd = globals.frontendLocalState.visibleWindowTime.end;
+          tEnd = visibleWindowTime.end;
         }
-        if (tStart <= t && t <= tEnd) {
+        if (tStart.lte(t) && t.lte(tEnd)) {
           return i;
         }
       }
@@ -433,19 +408,27 @@
     return SLICE_HEIGHT * (this.config.maxDepth + 1) + 2 * TRACK_PADDING;
   }
 
-  getSliceRect(tStart: number, tEnd: number, depth: number): SliceRect
+  getSliceRect(tStart: TPTime, tEnd: TPTime, 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.tpTimeToPx(tStart), 0);
+    const right = Math.min(timeScale.tpTimeToPx(tEnd), pxEnd);
+
+    const visible =
+        !(visibleWindowTime.start.gt(tEnd) || visibleWindowTime.end.lt(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 9aa8f74..966404c 100644
--- a/ui/src/tracks/counter/index.ts
+++ b/ui/src/tracks/counter/index.ts
@@ -19,17 +19,16 @@
 import {Actions} from '../../common/actions';
 import {
   EngineProxy,
+  LONG,
+  LONG_NULL,
   NUM,
-  NUM_NULL,
   PluginContext,
   STR,
   TrackInfo,
 } from '../../common/plugin_api';
-import {fromNs, toNs} from '../../common/time';
+import {TPDuration, TPTime, tpTimeToSeconds} from '../../common/time';
 import {TrackData} from '../../common/track_data';
-import {
-  TrackController,
-} from '../../controller/track_controller';
+import {TrackController} from '../../controller/track_controller';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
 import {NewTrackArgs, Track} from '../../frontend/track';
@@ -49,7 +48,7 @@
   minimumDelta: number;
   maximumRate: number;
   minimumRate: number;
-  timestamps: Float64Array;
+  timestamps: BigInt64Array;
   lastIds: Float64Array;
   minValues: Float64Array;
   maxValues: Float64Array;
@@ -62,8 +61,8 @@
   name: string;
   maximumValue?: number;
   minimumValue?: number;
-  startTs?: number;
-  endTs?: number;
+  startTs?: TPTime;
+  endTs?: TPTime;
   namespace: string;
   trackId: number;
   scale?: CounterScaleOptions;
@@ -76,19 +75,10 @@
   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);
-
     if (!this.setup) {
       if (this.config.namespace === undefined) {
         await this.query(`
@@ -123,7 +113,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
@@ -144,14 +134,14 @@
 
     const queryRes = await this.query(`
       select
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+        (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
         min(value) as minValue,
         max(value) as maxValue,
         sum(delta) as totalDelta,
         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
     `);
@@ -169,7 +159,7 @@
       maximumRate: 0,
       minimumRate: 0,
       resolution,
-      timestamps: new Float64Array(numRows),
+      timestamps: new BigInt64Array(numRows),
       lastIds: new Float64Array(numRows),
       minValues: new Float64Array(numRows),
       maxValues: new Float64Array(numRows),
@@ -179,7 +169,7 @@
     };
 
     const it = queryRes.iter({
-      'tsq': NUM,
+      'tsq': LONG,
       'lastId': NUM,
       'minValue': NUM,
       'maxValue': NUM,
@@ -187,11 +177,11 @@
       'totalDelta': NUM,
     });
     let lastValue = 0;
-    let lastTs = 0;
+    let lastTs = 0n;
     for (let row = 0; it.valid(); it.next(), row++) {
-      const ts = fromNs(it.tsq);
+      const ts = it.tsq;
       const value = it.lastValue;
-      const rate = (value - lastValue) / (ts - lastTs);
+      const rate = (value - lastValue) / (tpTimeToSeconds(ts - lastTs));
       lastTs = ts;
       lastValue = value;
 
@@ -241,8 +231,8 @@
 
   private mousePos = {x: 0, y: 0};
   private hoveredValue: number|undefined = undefined;
-  private hoveredTs: number|undefined = undefined;
-  private hoveredTsEnd: number|undefined = undefined;
+  private hoveredTs: bigint|undefined = undefined;
+  private hoveredTsEnd: bigint|undefined = undefined;
 
   constructor(args: NewTrackArgs) {
     super(args);
@@ -285,7 +275,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: timeScale,
+      windowSpan,
+    } = globals.frontendLocalState;
     const data = this.data();
 
     // Can't possibly draw anything.
@@ -321,7 +314,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).
@@ -365,8 +358,8 @@
     ctx.fillStyle = `hsl(${hue}, 45%, 75%)`;
     ctx.strokeStyle = `hsl(${hue}, 45%, 45%)`;
 
-    const calculateX = (ts: number) => {
-      return Math.floor(timeScale.timeToPx(ts));
+    const calculateX = (ts: TPTime) => {
+      return Math.floor(timeScale.tpTimeToPx(ts));
     };
     const calculateY = (value: number) => {
       return MARGIN_TOP + RECT_HEIGHT -
@@ -427,10 +420,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.tpTimeToPx(this.hoveredTs));
       const xEnd = this.hoveredTsEnd === undefined ?
           endPx :
-          Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
+          Math.floor(timeScale.tpTimeToPx(this.hoveredTsEnd));
       const y = MARGIN_TOP + RECT_HEIGHT -
           Math.round(((this.hoveredValue - yMin) / yRange) * RECT_HEIGHT);
 
@@ -463,9 +456,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) {
@@ -479,23 +473,23 @@
     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);
 
     const values = this.config.scale === 'DELTA_FROM_PREVIOUS' ?
         data.totalDeltas :
         data.lastValues;
-    const [left, right] = searchSegment(data.timestamps, time);
+    const [left, right] = searchSegment(data.timestamps, time.toTPTime());
     this.hoveredTs = left === -1 ? undefined : data.timestamps[left];
     this.hoveredTsEnd = right === -1 ? undefined : data.timestamps[right];
     this.hoveredValue = left === -1 ? undefined : values[left];
@@ -506,20 +500,20 @@
     this.hoveredTs = undefined;
   }
 
-  onMouseClick({x}: {x: number}) {
+  onMouseClick({x}: {x: number}): boolean {
     const data = this.data();
     if (data === undefined) return false;
-    const {timeScale} = globals.frontendLocalState;
-    const time = timeScale.pxToTime(x);
-    const [left, right] = searchSegment(data.timestamps, time);
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const time = visibleTimeScale.pxToHpTime(x);
+    const [left, right] = searchSegment(data.timestamps, time.toTPTime());
     if (left === -1) {
       return false;
     } else {
       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: data.timestamps[left],
+        rightTs: right !== -1 ? data.timestamps[right] : -1n,
         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..eec39e4 100644
--- a/ui/src/tracks/cpu_freq/index.ts
+++ b/ui/src/tracks/cpu_freq/index.ts
@@ -12,16 +12,21 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath as BIMath} 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 {TrackData} from '../../common/track_data';
 import {
-  TrackController,
-} from '../../controller/track_controller';
+  LONG,
+  LONG_NULL,
+  NUM,
+  NUM_NULL,
+  QueryResult,
+} from '../../common/query_result';
+import {TPDuration, TPTime} 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 {NewTrackArgs, Track} from '../../frontend/track';
@@ -31,9 +36,9 @@
 
 export interface Data extends TrackData {
   maximumValue: number;
-  maxTsEnd: number;
+  maxTsEnd: TPTime;
 
-  timestamps: Float64Array;
+  timestamps: BigInt64Array;
   minFreqKHz: Uint32Array;
   maxFreqKHz: Uint32Array;
   lastFreqKHz: Uint32Array;
@@ -51,39 +56,39 @@
 class CpuFreqTrackController extends TrackController<Config, Data> {
   static readonly kind = CPU_FREQ_TRACK_KIND;
 
-  private maxDurNs = 0;
-  private maxTsEndNs = 0;
+  private maxDur: TPDuration = 0n;
+  private maxTsEnd: TPTime = 0n;
   private maximumValueSeen = 0;
-  private cachedBucketNs = Number.MAX_SAFE_INTEGER;
+  private cachedBucketSize = BIMath.INT64_MAX;
 
   async onSetup() {
     await this.createFreqIdleViews();
 
     this.maximumValueSeen = await this.queryMaxFrequency();
-    this.maxDurNs = await this.queryMaxSourceDur();
+    this.maxDur = await this.queryMaxSourceDur();
 
     const iter = (await this.query(`
       select max(ts) as maxTs, dur, count(1) as rowCount
       from ${this.tableName('freq_idle')}
-    `)).firstRow({maxTs: NUM_NULL, dur: NUM_NULL, rowCount: NUM});
+    `)).firstRow({maxTs: LONG_NULL, dur: LONG_NULL, rowCount: NUM});
     if (iter.maxTs === null || iter.dur === null) {
       // We shoulnd't really hit this because trackDecider shouldn't create
       // the track in the first place if there are no entries. But could happen
       // if only one cpu has no cpufreq data.
       return;
     }
-    this.maxTsEndNs = iter.maxTs + iter.dur;
+    this.maxTsEnd = iter.maxTs + iter.dur;
 
     const rowCount = iter.rowCount;
-    const bucketNs = this.cachedBucketSizeNs(rowCount);
-    if (bucketNs === undefined) {
+    const bucketSize = this.calcCachedBucketSize(rowCount);
+    if (bucketSize === undefined) {
       return;
     }
 
     await this.query(`
       create table ${this.tableName('freq_idle_cached')} as
       select
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as cachedTsq,
+        (ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize} as cachedTsq,
         min(freqValue) as minFreq,
         max(freqValue) as maxFreq,
         value_at_max_ts(ts, freqValue) as lastFreq,
@@ -93,24 +98,16 @@
       order by cachedTsq
     `);
 
-    this.cachedBucketNs = bucketNs;
+    this.cachedBucketSize = bucketSize;
   }
 
-  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(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
 
-    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);
-    const freqResult = await this.queryData(startNs, endNs, bucketNs);
+    const freqResult = await this.queryData(start, end, resolution);
     assertTrue(freqResult.isComplete());
 
     const numRows = freqResult.numRows();
@@ -120,8 +117,8 @@
       resolution,
       length: numRows,
       maximumValue: this.maximumValue(),
-      maxTsEnd: this.maxTsEndNs,
-      timestamps: new Float64Array(numRows),
+      maxTsEnd: this.maxTsEnd,
+      timestamps: new BigInt64Array(numRows),
       minFreqKHz: new Uint32Array(numRows),
       maxFreqKHz: new Uint32Array(numRows),
       lastFreqKHz: new Uint32Array(numRows),
@@ -129,14 +126,14 @@
     };
 
     const it = freqResult.iter({
-      'tsq': NUM,
+      'tsq': LONG,
       'minFreq': NUM,
       'maxFreq': NUM,
       'lastFreq': NUM,
       'lastIdleValue': NUM,
     });
     for (let i = 0; it.valid(); ++i, it.next()) {
-      data.timestamps[i] = fromNs(it.tsq);
+      data.timestamps[i] = it.tsq;
       data.minFreqKHz[i] = it.minFreq;
       data.maxFreqKHz[i] = it.maxFreq;
       data.lastFreqKHz[i] = it.lastFreq;
@@ -146,36 +143,36 @@
     return data;
   }
 
-  private async queryData(startNs: number, endNs: number, bucketNs: number):
+  private async queryData(start: TPTime, end: TPTime, bucketSize: TPDuration):
       Promise<QueryResult> {
-    const isCached = this.cachedBucketNs <= bucketNs;
+    const isCached = this.cachedBucketSize <= bucketSize;
 
     if (isCached) {
       return this.query(`
         select
-          cachedTsq / ${bucketNs} * ${bucketNs} as tsq,
+          cachedTsq / ${bucketSize} * ${bucketSize} as tsq,
           min(minFreq) as minFreq,
           max(maxFreq) as maxFreq,
           value_at_max_ts(cachedTsq, lastFreq) as lastFreq,
           value_at_max_ts(cachedTsq, lastIdleValue) as lastIdleValue
         from ${this.tableName('freq_idle_cached')}
         where
-          cachedTsq >= ${startNs - this.maxDurNs} and
-          cachedTsq <= ${endNs}
+          cachedTsq >= ${start - this.maxDur} and
+          cachedTsq <= ${end}
         group by tsq
         order by tsq
       `);
     }
     const minTsFreq = await this.query(`
       select ifnull(max(ts), 0) as minTs from ${this.tableName('freq')}
-      where ts < ${startNs}
+      where ts < ${start}
     `);
 
     let minTs = minTsFreq.iter({minTs: NUM}).minTs;
     if (this.config.idleTrackId !== undefined) {
       const minTsIdle = await this.query(`
         select ifnull(max(ts), 0) as minTs from ${this.tableName('idle')}
-        where ts < ${startNs}
+        where ts < ${start}
       `);
       minTs = Math.min(minTsIdle.iter({minTs: NUM}).minTs, minTs);
     }
@@ -185,7 +182,7 @@
         `source_geq(ts, ${minTs})`;
     return this.query(`
       select
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+        (ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize} as tsq,
         min(freqValue) as minFreq,
         max(freqValue) as maxFreq,
         value_at_max_ts(ts, freqValue) as lastFreq,
@@ -193,7 +190,7 @@
       from ${this.tableName('freq_idle')}
       where
         ${geqConstraint} and
-        ts <= ${endNs}
+        ts <= ${end}
       group by tsq
       order by tsq
     `);
@@ -207,17 +204,17 @@
     return result.firstRow({'maxFreq': NUM_NULL}).maxFreq || 0;
   }
 
-  private async queryMaxSourceDur(): Promise<number> {
+  private async queryMaxSourceDur(): Promise<TPDuration> {
     const maxDurFreqResult = await this.query(
         `select ifnull(max(dur), 0) as maxDur from ${this.tableName('freq')}`);
-    const maxDurNs = maxDurFreqResult.firstRow({'maxDur': NUM}).maxDur;
+    const maxDur = maxDurFreqResult.firstRow({'maxDur': LONG}).maxDur;
     if (this.config.idleTrackId === undefined) {
-      return maxDurNs;
+      return maxDur;
     }
 
     const maxDurIdleResult = await this.query(
         `select ifnull(max(dur), 0) as maxDur from ${this.tableName('idle')}`);
-    return Math.max(maxDurNs, maxDurIdleResult.firstRow({maxDur: NUM}).maxDur);
+    return BIMath.max(maxDur, maxDurIdleResult.firstRow({maxDur: LONG}).maxDur);
   }
 
   private async createFreqIdleViews() {
@@ -275,8 +272,8 @@
 
   private mousePos = {x: 0, y: 0};
   private hoveredValue: number|undefined = undefined;
-  private hoveredTs: number|undefined = undefined;
-  private hoveredTsEnd: number|undefined = undefined;
+  private hoveredTs: TPTime|undefined = undefined;
+  private hoveredTsEnd: TPTime|undefined = undefined;
   private hoveredIdle: number|undefined = undefined;
 
   constructor(args: NewTrackArgs) {
@@ -289,7 +286,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 +303,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).
@@ -325,18 +326,19 @@
     ctx.fillStyle = `hsl(${hue}, ${saturation}%, 70%)`;
     ctx.strokeStyle = `hsl(${hue}, ${saturation}%, 55%)`;
 
-    const calculateX = (timestamp: number) => {
-      return Math.floor(timeScale.timeToPx(timestamp));
+    const calculateX = (timestamp: TPTime) => {
+      return Math.floor(visibleTimeScale.tpTimeToPx(timestamp));
     };
     const calculateY = (value: number) => {
       return zeroY - Math.round((value / yMax) * RECT_HEIGHT);
     };
 
-    const [rawStartIdx] =
-        searchSegment(data.timestamps, visibleWindowTime.start);
+    const start = visibleWindowTime.start;
+    const end = visibleWindowTime.end;
+    const [rawStartIdx] = searchSegment(data.timestamps, start.toTPTime());
     const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
 
-    const [, rawEndIdx] = searchSegment(data.timestamps, visibleWindowTime.end);
+    const [, rawEndIdx] = searchSegment(data.timestamps, end.toTPTime());
     const endIdx = rawEndIdx === -1 ? data.timestamps.length : rawEndIdx;
 
     ctx.beginPath();
@@ -383,10 +385,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.tpTimeToPx(data.timestamps[i]);
       const xEnd = i === data.lastIdleValues.length - 1 ?
           finalX :
-          timeScale.timeToPx(data.timestamps[i + 1]);
+          visibleTimeScale.tpTimeToPx(data.timestamps[i + 1]);
 
       const width = xEnd - x;
       const height = calculateY(data.lastFreqKHz[i]) - zeroY;
@@ -402,10 +404,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.tpTimeToPx(this.hoveredTs));
       const xEnd = this.hoveredTsEnd === undefined ?
           endPx :
-          Math.floor(timeScale.timeToPx(this.hoveredTsEnd));
+          Math.floor(visibleTimeScale.tpTimeToPx(this.hoveredTsEnd));
       const y = zeroY - Math.round((this.hoveredValue / yMax) * RECT_HEIGHT);
 
       // Highlight line.
@@ -446,20 +448,20 @@
     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);
 
-    const [left, right] = searchSegment(data.timestamps, time);
+    const [left, right] = searchSegment(data.timestamps, time.toTPTime());
     this.hoveredTs = left === -1 ? undefined : data.timestamps[left];
     this.hoveredTsEnd = right === -1 ? undefined : data.timestamps[right];
     this.hoveredValue = left === -1 ? undefined : data.lastFreqKHz[left];
diff --git a/ui/src/tracks/cpu_profile/index.ts b/ui/src/tracks/cpu_profile/index.ts
index eee7b17..abf2536 100644
--- a/ui/src/tracks/cpu_profile/index.ts
+++ b/ui/src/tracks/cpu_profile/index.ts
@@ -17,8 +17,8 @@
 import {Actions} from '../../common/actions';
 import {hslForSlice} from '../../common/colorizer';
 import {PluginContext} from '../../common/plugin_api';
-import {NUM} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {LONG, NUM} from '../../common/query_result';
+import {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -36,7 +36,7 @@
 
 export interface Data extends TrackData {
   ids: Float64Array;
-  tsStarts: Float64Array;
+  tsStarts: BigInt64Array;
   callsiteId: Uint32Array;
 }
 
@@ -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,
@@ -64,11 +64,11 @@
       resolution,
       length: numRows,
       ids: new Float64Array(numRows),
-      tsStarts: new Float64Array(numRows),
+      tsStarts: new BigInt64Array(numRows),
       callsiteId: new Uint32Array(numRows),
     };
 
-    const it = result.iter({id: NUM, ts: NUM, callsiteId: NUM});
+    const it = result.iter({id: NUM, ts: LONG, callsiteId: NUM});
     for (let row = 0; it.valid(); it.next(), ++row) {
       data.ids[row] = it.id;
       data.tsStarts[row] = it.ts;
@@ -93,7 +93,7 @@
 
   private centerY = this.getHeight() / 2 + BAR_HEIGHT;
   private markerWidth = (this.getHeight() - MARGIN_TOP - BAR_HEIGHT) / 2;
-  private hoveredTs: number|undefined = undefined;
+  private hoveredTs: TPTime|undefined = undefined;
 
   constructor(args: NewTrackArgs) {
     super(args);
@@ -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.tpTimeToPx(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.tpTimeToPx(startX) - this.markerWidth;
+        const rightPx = timeScale.tpTimeToPx(endX) + this.markerWidth;
         const width = rightPx - leftPx;
         ctx.fillStyle = colorForSample(callsiteId, false);
         ctx.fillRect(leftPx, MARGIN_TOP, width, BAR_HEIGHT);
@@ -179,9 +179,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 [left, right] = searchSegment(data.tsStarts, time);
+    const {
+      visibleTimeScale: timeScale,
+    } = globals.frontendLocalState;
+    const time = timeScale.pxToHpTime(x);
+    const [left, right] = searchSegment(data.tsStarts, time.toTPTime());
     const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
     this.hoveredTs = index === -1 ? undefined : data.tsStarts[index];
   }
@@ -193,10 +195,12 @@
   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 [left, right] = searchSegment(data.tsStarts, time);
+    const time = timeScale.pxToHpTime(x);
+    const [left, right] = searchSegment(data.tsStarts, time.toTPTime());
 
     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.tpTimeToPx(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.tpTimeToPx(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..2b2d03d 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 as BIMath} from '../../base/bigint_math';
 import {search, searchEq, searchSegment} from '../../base/binary_search';
 import {assertTrue} from '../../base/logging';
 import {Actions} from '../../common/actions';
@@ -22,8 +23,12 @@
 } from '../../common/canvas_utils';
 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 {LONG, NUM} from '../../common/query_result';
+import {
+  TPDuration,
+  TPTime,
+  tpTimeToString,
+} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {
   TrackController,
@@ -37,8 +42,8 @@
 export interface Data extends TrackData {
   // Slices are stored in a columnar fashion. All fields have the same length.
   ids: Float64Array;
-  starts: Float64Array;
-  ends: Float64Array;
+  starts: BigInt64Array;
+  ends: BigInt64Array;
   utids: Uint32Array;
   isIncomplete: Uint8Array;
   lastRowId: number;
@@ -51,8 +56,8 @@
 class CpuSliceTrackController extends TrackController<Config, Data> {
   static readonly kind = CPU_SLICE_TRACK_KIND;
 
-  private cachedBucketNs = Number.MAX_SAFE_INTEGER;
-  private maxDurNs = 0;
+  private cachedBucketSize = BIMath.INT64_MAX;
+  private maxDur: TPDuration = 0n;
   private lastRowId = -1;
 
   async onSetup() {
@@ -78,18 +83,18 @@
     `);
     this.lastRowId = queryLastSlice.firstRow({lastSliceId: NUM}).lastSliceId;
 
-    const row = queryRes.firstRow({maxDur: NUM, rowCount: NUM});
-    this.maxDurNs = row.maxDur;
+    const row = queryRes.firstRow({maxDur: LONG, rowCount: NUM});
+    this.maxDur = row.maxDur;
     const rowCount = row.rowCount;
-    const bucketNs = this.cachedBucketSizeNs(rowCount);
-    if (bucketNs === undefined) {
+    const bucketSize = this.calcCachedBucketSize(rowCount);
+    if (bucketSize === undefined) {
       return;
     }
 
     await this.query(`
       create table ${this.tableName('sched_cached')} as
       select
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as cached_tsq,
+        (ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize} as cached_tsq,
         ts,
         max(dur) as dur,
         utid,
@@ -99,29 +104,17 @@
       group by cached_tsq, isIncomplete
       order by cached_tsq
     `);
-    this.cachedBucketNs = bucketNs;
+    this.cachedBucketSize = bucketSize;
   }
 
-  async onBoundsChange(start: number, end: number, resolution: number):
+  async onBoundsChange(start: TPTime, end: TPTime, resolution: TPDuration):
       Promise<Data> {
-    const resolutionNs = toNs(resolution);
+    assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
 
-    // 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);
-
-    // 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);
-
-    const isCached = this.cachedBucketNs <= bucketNs;
+    const isCached = this.cachedBucketSize <= resolution;
     const queryTsq = isCached ?
-        `cached_tsq / ${bucketNs} * ${bucketNs}` :
-        `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`;
+        `cached_tsq / ${resolution} * ${resolution}` :
+        `(ts + ${resolution / 2n}) / ${resolution} * ${resolution}`;
     const queryTable =
         isCached ? this.tableName('sched_cached') : this.tableName('sched');
     const constraintColumn = isCached ? 'cached_tsq' : 'ts';
@@ -136,8 +129,8 @@
         isIncomplete
       from ${queryTable}
       where
-        ${constraintColumn} >= ${boundStartNs - this.maxDurNs} and
-        ${constraintColumn} <= ${boundEndNs}
+        ${constraintColumn} >= ${start - this.maxDur} and
+        ${constraintColumn} <= ${end}
       group by tsq, isIncomplete
       order by tsq
     `);
@@ -150,29 +143,34 @@
       length: numRows,
       lastRowId: this.lastRowId,
       ids: new Float64Array(numRows),
-      starts: new Float64Array(numRows),
-      ends: new Float64Array(numRows),
+      starts: new BigInt64Array(numRows),
+      ends: new BigInt64Array(numRows),
       utids: new Uint32Array(numRows),
       isIncomplete: new Uint8Array(numRows),
     };
 
-    const it = queryRes.iter(
-        {tsq: NUM, ts: NUM, dur: NUM, utid: NUM, id: NUM, isIncomplete: NUM});
+    const it = queryRes.iter({
+      tsq: LONG,
+      ts: LONG,
+      dur: LONG,
+      utid: NUM,
+      id: NUM,
+      isIncomplete: NUM,
+    });
     for (let row = 0; it.valid(); it.next(), row++) {
-      const startNsQ = it.tsq;
-      const startNs = it.ts;
-      const durNs = it.dur;
-      const endNs = startNs + durNs;
+      const startQ = it.tsq;
+      const start = it.ts;
+      const dur = it.dur;
+      const end = start + dur;
 
       // If the slice is incomplete, the end calculated later.
       if (!it.isIncomplete) {
-        let endNsQ =
-            Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
-        endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
-        slices.ends[row] = fromNs(endNsQ);
+        const minEnd = startQ + resolution;
+        const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
+        slices.ends[row] = endQ;
       }
 
-      slices.starts[row] = fromNs(startNsQ);
+      slices.starts[row] = startQ;
       slices.utids[row] = it.utid;
       slices.ids[row] = it.id;
       slices.isIncomplete[row] = it.isIncomplete;
@@ -185,12 +183,10 @@
       if (!slices.isIncomplete[row]) {
         continue;
       }
-      const endNs =
-          row === slices.length - 1 ? boundEndNs : toNs(slices.starts[row + 1]);
-
-      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
-      endNsQ = Math.max(endNsQ, toNs(slices.starts[row]) + bucketNs);
-      slices.ends[row] = fromNs(endNsQ);
+      const endTime = row === slices.length - 1 ? end : slices.starts[row + 1];
+      const minEnd = slices.starts[row] + resolution;
+      const endQ = BIMath.max(BIMath.quant(endTime, resolution), minEnd);
+      slices.ends[row] = endQ;
     }
     return slices;
   }
@@ -223,7 +219,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 +229,36 @@
     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,
+      visibleTimeSpan,
+      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 startTime = visibleTimeSpan.start;
+    const endTime = visibleTimeSpan.end;
+
+    const rawStartIdx = data.ends.findIndex((end) => end >= startTime);
     const startIdx = rawStartIdx === -1 ? 0 : rawStartIdx;
 
-    const [, rawEndIdx] = searchSegment(data.starts, visibleWindowTime.end);
+    const [, rawEndIdx] = searchSegment(data.starts, endTime);
     const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx;
 
     for (let i = startIdx; i < endIdx; i++) {
@@ -266,10 +270,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 = endTime;
       }
-      const rectStart = timeScale.timeToPx(tStart);
-      const rectEnd = timeScale.timeToPx(tEnd);
+      const rectStart = visibleTimeScale.tpTimeToPx(tStart);
+      const rectEnd = visibleTimeScale.tpTimeToPx(tEnd);
       const rectWidth = Math.max(1, rectEnd - rectStart);
 
       const threadInfo = globals.threads.get(utid);
@@ -317,8 +321,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 +344,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.tpTimeToPx(tStart);
+        const rectEnd = visibleTimeScale.tpTimeToPx(tEnd);
         const rectWidth = Math.max(1, rectEnd - rectStart);
 
         // Draw a rectangle around the slice that is currently selected.
@@ -353,7 +356,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 +365,8 @@
               latencyWidth,
               latencyWidth >= 20);
           // Latency time with a white semi-transparent background.
-          const displayText = timeToString(tStart - details.wakeupTs);
+          const latency = 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 +387,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,20 +416,20 @@
     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);
     let hoveredUtid = -1;
 
     for (let i = 0; i < data.starts.length; i++) {
       const tStart = data.starts[i];
       const tEnd = data.ends[i];
       const utid = data.utids[i];
-      if (tStart <= t && t <= tEnd) {
+      if (t.gte(tStart) && t.lt(tEnd)) {
         hoveredUtid = utid;
         break;
       }
@@ -445,9 +450,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.toTPTime());
     const id = index === -1 ? undefined : data.ids[index];
     if (!id || this.utidHoveredInThisTrack === -1) return false;
     globals.makeSelection(
diff --git a/ui/src/tracks/debug/add_debug_track_menu.ts b/ui/src/tracks/debug/add_debug_track_menu.ts
index 2982773..32bc871 100644
--- a/ui/src/tracks/debug/add_debug_track_menu.ts
+++ b/ui/src/tracks/debug/add_debug_track_menu.ts
@@ -89,7 +89,7 @@
         m(FormLabel,
           {for: 'track_name',
           },
-          'Name'),
+          'Track name'),
         m(TextInput, {
           id: 'track_name',
           onkeydown: (e: KeyboardEvent) => {
@@ -109,7 +109,7 @@
             FormButtonBar,
             m(Button, {
               label: 'Show',
-              className: 'pf-close-parent-popup-on-click',
+              dismissPopup: true,
               onclick: (e: Event) => {
                 e.preventDefault();
                 addDebugTrack(
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..3b7d7e2 100644
--- a/ui/src/tracks/expected_frames/index.ts
+++ b/ui/src/tracks/expected_frames/index.ts
@@ -19,12 +19,13 @@
 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, LONG_NULL, NUM, STR} from '../../common/query_result';
+import {TPDuration, TPTime} from '../../common/time';
 import {
   TrackController,
 } from '../../controller/track_controller';
 import {PluginContext} from '../../common/plugin_api';
+import {BigintMath as BIMath} from '../../base/bigint_math';
 
 export interface Config {
   maxDepth: number;
@@ -35,8 +36,8 @@
   // Slices are stored in a columnar fashion. All fields have the same length.
   strings: string[];
   sliceIds: Float64Array;
-  starts: Float64Array;
-  ends: Float64Array;
+  starts: BigInt64Array;
+  ends: BigInt64Array;
   depths: Uint16Array;
   titles: Uint16Array;   // Index in |strings|.
   colors?: Uint16Array;  // Index in |strings|.
@@ -46,32 +47,23 @@
 
 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);
-
-    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(`
       SELECT
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+        (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
         ts,
         max(iif(dur = -1, (SELECT end_ts FROM trace_bounds) - ts, dur)) as dur,
         layout_depth as layoutDepth,
@@ -82,8 +74,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
     `);
@@ -96,8 +88,8 @@
       length: numRows,
       strings: [],
       sliceIds: new Float64Array(numRows),
-      starts: new Float64Array(numRows),
-      ends: new Float64Array(numRows),
+      starts: new BigInt64Array(numRows),
+      ends: new BigInt64Array(numRows),
       depths: new Uint16Array(numRows),
       titles: new Uint16Array(numRows),
       colors: new Uint16Array(numRows),
@@ -117,9 +109,9 @@
     const greenIndex = internString('#4CAF50');
 
     const it = queryRes.iter({
-      tsq: NUM,
-      ts: NUM,
-      dur: NUM,
+      tsq: LONG,
+      ts: LONG,
+      dur: LONG,
       layoutDepth: NUM,
       id: NUM,
       name: STR,
@@ -127,16 +119,15 @@
       isIncomplete: NUM,
     });
     for (let row = 0; it.valid(); it.next(), ++row) {
-      const startNsQ = it.tsq;
-      const startNs = it.ts;
-      const durNs = it.dur;
-      const endNs = startNs + durNs;
+      const startQ = it.tsq;
+      const start = it.ts;
+      const dur = it.dur;
+      const end = start + dur;
+      const minEnd = startQ + resolution;
+      const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
 
-      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
-      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
-
-      slices.starts[row] = fromNs(startNsQ);
-      slices.ends[row] = fromNs(endNsQ);
+      slices.starts[row] = startQ;
+      slices.ends[row] = endQ;
       slices.depths[row] = it.layoutDepth;
       slices.titles[row] = internString(it.name);
       slices.sliceIds[row] = it.id;
diff --git a/ui/src/tracks/ftrace/index.ts b/ui/src/tracks/ftrace/index.ts
index e5913fa..fc0b9e8 100644
--- a/ui/src/tracks/ftrace/index.ts
+++ b/ui/src/tracks/ftrace/index.ts
@@ -16,8 +16,9 @@
 
 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 {LONG, STR} from '../../common/query_result';
+import {TPDuration} from '../../common/time';
+import {TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {LIMIT} from '../../common/track_data';
 import {
@@ -29,7 +30,7 @@
 
 
 export interface Data extends TrackData {
-  timestamps: Float64Array;
+  timestamps: BigInt64Array;
   names: string[];
 }
 
@@ -46,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 =
@@ -61,13 +56,13 @@
 
     const queryRes = await this.query(`
       select
-        cast(ts / ${quantNs} as integer) * ${quantNs} as tsQuant,
+        cast(ts / ${resolution} as integer) * ${resolution} as tsQuant,
         type,
         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};`);
 
@@ -77,15 +72,15 @@
       end,
       resolution,
       length: rowCount,
-      timestamps: new Float64Array(rowCount),
+      timestamps: new BigInt64Array(rowCount),
       names: [],
     };
 
     const it = queryRes.iter(
-        {tsQuant: NUM, type: STR, name: STR},
+        {tsQuant: LONG, type: STR, name: STR},
     );
     for (let row = 0; it.valid(); it.next(), row++) {
-      result.timestamps[row] = fromNs(it.tsQuant);
+      result.timestamps[row] = it.tsQuant;
       result.names[row] = it.name;
     }
     return result;
@@ -107,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,
@@ -137,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.tpTimeToPx(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..2de8802 100644
--- a/ui/src/tracks/heap_profile/index.ts
+++ b/ui/src/tracks/heap_profile/index.ts
@@ -17,13 +17,11 @@
 import {searchSegment} from '../../base/binary_search';
 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 {LONG, STR} from '../../common/query_result';
+import {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {profileType} from '../../controller/flamegraph_controller';
-import {
-  TrackController,
-} from '../../controller/track_controller';
+import {TrackController} from '../../controller/track_controller';
 import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
 import {globals} from '../../frontend/globals';
 import {TimeScale} from '../../frontend/time_scale';
@@ -32,7 +30,7 @@
 export const HEAP_PROFILE_TRACK_KIND = 'HeapProfileTrack';
 
 export interface Data extends TrackData {
-  tsStarts: Float64Array;
+  tsStarts: BigInt64Array;
   types: ProfileType[];
 }
 
@@ -42,7 +40,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 {
@@ -50,7 +48,7 @@
         end,
         resolution,
         length: 0,
-        tsStarts: new Float64Array(),
+        tsStarts: new BigInt64Array(),
         types: new Array<ProfileType>(),
       };
     }
@@ -73,11 +71,11 @@
       end,
       resolution,
       length: numRows,
-      tsStarts: new Float64Array(numRows),
+      tsStarts: new BigInt64Array(numRows),
       types: new Array<ProfileType>(numRows),
     };
 
-    const it = queryRes.iter({ts: NUM, type: STR});
+    const it = queryRes.iter({ts: LONG, type: STR});
     for (let row = 0; it.valid(); it.next(), row++) {
       data.tsStarts[row] = it.ts;
       data.types[row] = profileType(it.type);
@@ -99,7 +97,7 @@
 
   private centerY = this.getHeight() / 2;
   private markerWidth = (this.getHeight() - MARGIN_TOP) / 2;
-  private hoveredTs: number|undefined = undefined;
+  private hoveredTs: bigint|undefined = undefined;
 
   constructor(args: NewTrackArgs) {
     super(args);
@@ -111,7 +109,7 @@
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     const {
-      timeScale,
+      visibleTimeScale: timeScale,
     } = globals.frontendLocalState;
     const data = this.data();
 
@@ -126,7 +124,7 @@
       const strokeWidth = isSelected ? 3 : 0;
       this.drawMarker(
           ctx,
-          timeScale.timeToPx(fromNs(centerX)),
+          timeScale.tpTimeToPx(centerX),
           this.centerY,
           isHovered,
           strokeWidth);
@@ -155,9 +153,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 [left, right] = searchSegment(data.tsStarts, time);
+    const {
+      visibleTimeScale: timeScale,
+    } = globals.frontendLocalState;
+    const time = timeScale.pxToHpTime(x);
+    const [left, right] = searchSegment(data.tsStarts, time.toTPTime());
     const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
     this.hoveredTs = index === -1 ? undefined : data.tsStarts[index];
   }
@@ -169,10 +169,12 @@
   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 [left, right] = searchSegment(data.tsStarts, time);
+    const time = timeScale.pxToHpTime(x);
+    const [left, right] = searchSegment(data.tsStarts, time.toTPTime());
 
     const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
 
@@ -192,13 +194,13 @@
       right: number): number {
     let index = -1;
     if (left !== -1) {
-      const centerX = timeScale.timeToPx(fromNs(data.tsStarts[left]));
+      const centerX = timeScale.tpTimeToPx(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.tpTimeToPx(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..21b13eb 100644
--- a/ui/src/tracks/perf_samples_profile/index.ts
+++ b/ui/src/tracks/perf_samples_profile/index.ts
@@ -15,13 +15,11 @@
 import {searchSegment} from '../../base/binary_search';
 import {Actions} from '../../common/actions';
 import {PluginContext} from '../../common/plugin_api';
-import {NUM} from '../../common/query_result';
+import {LONG} from '../../common/query_result';
 import {ProfileType} from '../../common/state';
-import {fromNs, toNs} from '../../common/time';
+import {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
-import {
-  TrackController,
-} from '../../controller/track_controller';
+import {TrackController} from '../../controller/track_controller';
 import {FLAMEGRAPH_HOVERED_COLOR} from '../../frontend/flamegraph';
 import {globals} from '../../frontend/globals';
 import {TimeScale} from '../../frontend/time_scale';
@@ -30,7 +28,7 @@
 export const PERF_SAMPLES_PROFILE_TRACK_KIND = 'PerfSamplesProfileTrack';
 
 export interface Data extends TrackData {
-  tsStartsNs: Float64Array;
+  tsStarts: BigInt64Array;
 }
 
 export interface Config {
@@ -39,7 +37,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 {
@@ -47,7 +45,7 @@
         end,
         resolution,
         length: 0,
-        tsStartsNs: new Float64Array(),
+        tsStarts: new BigInt64Array(),
       };
     }
     const queryRes = await this.query(`
@@ -62,12 +60,12 @@
       end,
       resolution,
       length: numRows,
-      tsStartsNs: new Float64Array(numRows),
+      tsStarts: new BigInt64Array(numRows),
     };
 
-    const it = queryRes.iter({ts: NUM});
+    const it = queryRes.iter({ts: LONG});
     for (let row = 0; it.valid(); it.next(), row++) {
-      data.tsStartsNs[row] = it.ts;
+      data.tsStarts[row] = it.ts;
     }
     return data;
   }
@@ -87,7 +85,7 @@
 
   private centerY = this.getHeight() / 2;
   private markerWidth = (this.getHeight() - MARGIN_TOP) / 2;
-  private hoveredTs: number|undefined = undefined;
+  private hoveredTs: TPTime|undefined = undefined;
 
   constructor(args: NewTrackArgs) {
     super(args);
@@ -99,14 +97,14 @@
 
   renderCanvas(ctx: CanvasRenderingContext2D): void {
     const {
-      timeScale,
+      visibleTimeScale,
     } = globals.frontendLocalState;
     const data = this.data();
 
     if (data === undefined) return;
 
-    for (let i = 0; i < data.tsStartsNs.length; i++) {
-      const centerX = data.tsStartsNs[i];
+    for (let i = 0; i < data.tsStarts.length; i++) {
+      const centerX = data.tsStarts[i];
       const selection = globals.state.currentSelection;
       const isHovered = this.hoveredTs === centerX;
       const isSelected = selection !== null &&
@@ -115,7 +113,7 @@
       const strokeWidth = isSelected ? 3 : 0;
       this.drawMarker(
           ctx,
-          timeScale.timeToPx(fromNs(centerX)),
+          visibleTimeScale.tpTimeToPx(centerX),
           this.centerY,
           isHovered,
           strokeWidth);
@@ -144,11 +142,12 @@
   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 [left, right] = searchSegment(data.tsStartsNs, time);
-    const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
-    this.hoveredTs = index === -1 ? undefined : data.tsStartsNs[index];
+    const {visibleTimeScale} = globals.frontendLocalState;
+    const time = visibleTimeScale.pxToHpTime(x);
+    const [left, right] = searchSegment(data.tsStarts, time.toTPTime());
+    const index =
+        this.findTimestampIndex(left, visibleTimeScale, data, x, y, right);
+    this.hoveredTs = index === -1 ? undefined : data.tsStarts[index];
   }
 
   onMouseOut() {
@@ -158,15 +157,16 @@
   onMouseClick({x, y}: {x: number, y: number}) {
     const data = this.data();
     if (data === undefined) return false;
-    const {timeScale} = globals.frontendLocalState;
+    const {visibleTimeScale} = globals.frontendLocalState;
 
-    const time = toNs(timeScale.pxToTime(x));
-    const [left, right] = searchSegment(data.tsStartsNs, time);
+    const time = visibleTimeScale.pxToHpTime(x);
+    const [left, right] = searchSegment(data.tsStarts, time.toTPTime());
 
-    const index = this.findTimestampIndex(left, timeScale, data, x, y, right);
+    const index =
+        this.findTimestampIndex(left, visibleTimeScale, data, x, y, right);
 
     if (index !== -1) {
-      const ts = data.tsStartsNs[index];
+      const ts = data.tsStarts[index];
       globals.makeSelection(Actions.selectPerfSamples({
         id: index,
         upid: this.config.upid,
@@ -185,13 +185,13 @@
       right: number): number {
     let index = -1;
     if (left !== -1) {
-      const centerX = timeScale.timeToPx(fromNs(data.tsStartsNs[left]));
+      const centerX = timeScale.tpTimeToPx(data.tsStarts[left]);
       if (this.isInMarker(x, y, centerX)) {
         index = left;
       }
     }
     if (right !== -1) {
-      const centerX = timeScale.timeToPx(fromNs(data.tsStartsNs[right]));
+      const centerX = timeScale.tpTimeToPx(data.tsStarts[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..270157c 100644
--- a/ui/src/tracks/process_scheduling/index.ts
+++ b/ui/src/tracks/process_scheduling/index.ts
@@ -12,17 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath as BIMath} 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 {LONG, NUM, QueryResult} from '../../common/query_result';
+import {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
-import {
-  TrackController,
-} from '../../controller/track_controller';
+import {TrackController} from '../../controller/track_controller';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
 import {NewTrackArgs, Track} from '../../frontend/track';
@@ -34,8 +34,8 @@
   maxCpu: number;
 
   // Slices are stored in a columnar fashion. All fields have the same length.
-  starts: Float64Array;
-  ends: Float64Array;
+  starts: BigInt64Array;
+  ends: BigInt64Array;
   utids: Uint32Array;
   cpus: Uint32Array;
 }
@@ -52,8 +52,8 @@
   static readonly kind = PROCESS_SCHEDULING_TRACK_KIND;
 
   private maxCpu = 0;
-  private maxDurNs = 0;
-  private cachedBucketNs = Number.MAX_SAFE_INTEGER;
+  private maxDur = 0n;
+  private cachedBucketSize = BIMath.INT64_MAX;
 
   async onSetup() {
     await this.createSchedView();
@@ -67,19 +67,19 @@
     const result = (await this.query(`
       select ifnull(max(dur), 0) as maxDur, count(1) as count
       from ${this.tableName('process_sched')}
-    `)).iter({maxDur: NUM, count: NUM});
+    `)).iter({maxDur: LONG, count: NUM});
     assertTrue(result.valid());
-    this.maxDurNs = result.maxDur;
+    this.maxDur = result.maxDur;
 
     const rowCount = result.count;
-    const bucketNs = this.cachedBucketSizeNs(rowCount);
-    if (bucketNs === undefined) {
+    const bucketSize = this.calcCachedBucketSize(rowCount);
+    if (bucketSize === undefined) {
       return;
     }
     await this.query(`
       create table ${this.tableName('process_sched_cached')} as
       select
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as cached_tsq,
+        (ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize} as cached_tsq,
         ts,
         max(dur) as dur,
         cpu,
@@ -88,27 +88,17 @@
       group by cached_tsq, cpu
       order by cached_tsq, cpu
     `);
-    this.cachedBucketNs = bucketNs;
+    this.cachedBucketSize = bucketSize;
   }
 
-  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);
+    // Resolution must always be a power of 2 for this logic to work
+    assertTrue(BIMath.popcount(resolution) === 1, `${resolution} not pow of 2`);
 
-    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);
-
-    const queryRes = await this.queryData(startNs, endNs, bucketNs);
+    const queryRes = await this.queryData(start, end, resolution);
     const numRows = queryRes.numRows();
     const slices: Data = {
       kind: 'slice',
@@ -117,46 +107,48 @@
       resolution,
       length: numRows,
       maxCpu: this.maxCpu,
-      starts: new Float64Array(numRows),
-      ends: new Float64Array(numRows),
+      starts: new BigInt64Array(numRows),
+      ends: new BigInt64Array(numRows),
       cpus: new Uint32Array(numRows),
       utids: new Uint32Array(numRows),
     };
 
     const it = queryRes.iter({
-      tsq: NUM,
-      ts: NUM,
-      dur: NUM,
+      tsq: LONG,
+      ts: LONG,
+      dur: LONG,
       cpu: NUM,
       utid: NUM,
     });
 
     for (let row = 0; it.valid(); it.next(), row++) {
-      const startNsQ = it.tsq;
-      const startNs = it.ts;
-      const durNs = it.dur;
-      const endNs = startNs + durNs;
+      const startQ = it.tsq;
+      const start = it.ts;
+      const dur = it.dur;
+      const end = start + dur;
+      const minEnd = startQ + resolution;
+      const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
 
-      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
-      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
-
-      slices.starts[row] = fromNs(startNsQ);
-      slices.ends[row] = fromNs(endNsQ);
+      slices.starts[row] = startQ;
+      slices.ends[row] = endQ;
       slices.cpus[row] = it.cpu;
       slices.utids[row] = it.utid;
-      slices.end = Math.max(slices.ends[row], slices.end);
+      slices.end = BIMath.max(slices.ends[row], slices.end);
     }
     return slices;
   }
 
-  private queryData(startNs: number, endNs: number, bucketNs: number):
+  private queryData(start: TPTime, end: TPTime, bucketSize: TPDuration):
       Promise<QueryResult> {
-    const isCached = this.cachedBucketNs <= bucketNs;
-    const tsq = isCached ? `cached_tsq / ${bucketNs} * ${bucketNs}` :
-                           `(ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs}`;
+    const isCached = this.cachedBucketSize <= bucketSize;
+    const tsq = isCached ?
+        `cached_tsq / ${bucketSize} * ${bucketSize}` :
+        `(ts + ${bucketSize / 2n}) / ${bucketSize} * ${bucketSize}`;
     const queryTable = isCached ? this.tableName('process_sched_cached') :
                                   this.tableName('process_sched');
     const constraintColumn = isCached ? 'cached_tsq' : 'ts';
+
+    // The mouse move handler depends on slices being sorted by cpu then tsq
     return this.query(`
       select
         ${tsq} as tsq,
@@ -166,10 +158,10 @@
         utid
       from ${queryTable}
       where
-        ${constraintColumn} >= ${startNs - this.maxDurNs} and
-        ${constraintColumn} <= ${endNs}
+        ${constraintColumn} >= ${start - this.maxDur} and
+        ${constraintColumn} <= ${end}
       group by tsq, cpu
-      order by tsq, cpu
+      order by cpu, tsq
     `);
   }
 
@@ -208,7 +200,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 +213,21 @@
     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 startTime = visibleWindowTime.start.toTPTime('floor');
+    const rawStartIdx = data.ends.findIndex((end) => end >= startTime);
     const startIdx = rawStartIdx === -1 ? data.starts.length : rawStartIdx;
 
-    const [, rawEndIdx] = searchSegment(data.starts, visibleWindowTime.end);
+
+    const endTime = visibleWindowTime.end.toTPTime('ceil');
+    const [, rawEndIdx] = searchSegment(data.starts, endTime);
     const endIdx = rawEndIdx === -1 ? data.starts.length : rawEndIdx;
 
     const cpuTrackHeight = Math.floor(RECT_HEIGHT / data.maxCpu);
@@ -241,8 +238,8 @@
       const utid = data.utids[i];
       const cpu = data.cpus[i];
 
-      const rectStart = timeScale.timeToPx(tStart);
-      const rectEnd = timeScale.timeToPx(tEnd);
+      const rectStart = visibleTimeScale.tpTimeToPx(tStart);
+      const rectEnd = visibleTimeScale.tpTimeToPx(tEnd);
       const rectWidth = rectEnd - rectStart;
       if (rectWidth < 0.3) continue;
 
@@ -294,8 +291,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).toTPTime('floor');
 
     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..0697ce1 100644
--- a/ui/src/tracks/process_summary/index.ts
+++ b/ui/src/tracks/process_summary/index.ts
@@ -12,15 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath} from '../../base/bigint_math';
+import {assertFalse} from '../../base/logging';
 import {colorForTid} from '../../common/colorizer';
 import {PluginContext} from '../../common/plugin_api';
 import {NUM} from '../../common/query_result';
-import {fromNs, toNs} from '../../common/time';
+import {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {LIMIT} from '../../common/track_data';
-import {
-  TrackController,
-} from '../../controller/track_controller';
+import {TrackController} from '../../controller/track_controller';
 import {checkerboardExcept} from '../../frontend/checkerboard';
 import {globals} from '../../frontend/globals';
 import {NewTrackArgs, Track} from '../../frontend/track';
@@ -29,7 +29,7 @@
 
 // TODO(dproy): Consider deduping with CPU summary data.
 export interface Data extends TrackData {
-  bucketSizeSeconds: number;
+  bucketSize: TPDuration;
   utilizations: Float64Array;
 }
 
@@ -45,10 +45,9 @@
   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);
+    assertFalse(resolution === 0n, 'Resolution cannot be 0');
 
     if (this.setup === false) {
       await this.query(
@@ -85,33 +84,30 @@
       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 windowStartNs = Math.floor(startNs / bucketSizeNs) * bucketSizeNs;
-    const windowDurNs = Math.max(1, endNs - windowStartNs);
+    const bucketSize = resolution * 10n;
+    const windowStart = BigintMath.quant(start, bucketSize);
+    const windowDur = BigintMath.max(1n, end - windowStart);
 
     await this.query(`update ${this.tableName('window')} set
-      window_start=${windowStartNs},
-      window_dur=${windowDurNs},
-      quantum=${bucketSizeNs}
+      window_start=${windowStart},
+      window_dur=${windowDur},
+      quantum=${bucketSize}
       where rowid = 0;`);
 
-    return this.computeSummary(
-        fromNs(windowStartNs), end, resolution, bucketSizeNs);
+    return this.computeSummary(windowStart, end, resolution, bucketSize);
   }
 
   private async computeSummary(
-      start: number, end: number, resolution: number,
-      bucketSizeNs: number): Promise<Data> {
-    const startNs = toNs(start);
-    const endNs = toNs(end);
-    const numBuckets =
-        Math.min(Math.ceil((endNs - startNs) / bucketSizeNs), LIMIT);
+      start: TPTime, end: TPTime, resolution: TPDuration,
+      bucketSize: TPDuration): Promise<Data> {
+    const duration = end - start;
+    const numBuckets = Math.min(Number(duration / bucketSize), LIMIT);
 
     const query = `select
       quantum_ts as bucket,
-      sum(dur)/cast(${bucketSizeNs} as float) as utilization
+      sum(dur)/cast(${bucketSize} as float) as utilization
       from ${this.tableName('span')}
       group by quantum_ts
       limit ${LIMIT}`;
@@ -121,7 +117,7 @@
       end,
       resolution,
       length: numBuckets,
-      bucketSizeSeconds: fromNs(bucketSizeNs),
+      bucketSize,
       utilizations: new Float64Array(numBuckets),
     };
 
@@ -167,25 +163,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 +201,9 @@
     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 = BigInt(i) * data.bucketSize + data.start;
 
-      lastX = Math.floor(timeScale.timeToPx(startTime));
+      lastX = Math.floor(visibleTimeScale.tpTimeToPx(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..93dc87d
--- /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 {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 {TPTime} from '../../common/time';
+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..5e90661 100644
--- a/ui/src/tracks/thread_state/index.ts
+++ b/ui/src/tracks/thread_state/index.ts
@@ -12,15 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import {BigintMath as BIMath} from '../../base/bigint_math';
 import {search} from '../../base/binary_search';
 import {assertFalse} from '../../base/logging';
 import {Actions} from '../../common/actions';
 import {cropText} from '../../common/canvas_utils';
 import {colorForState} from '../../common/colorizer';
+import {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 {TPDuration, TPTime} from '../../common/time';
 import {TrackData} from '../../common/track_data';
 import {TrackController} from '../../controller/track_controller';
 import {checkerboardExcept} from '../../frontend/checkerboard';
@@ -33,8 +35,8 @@
 export interface Data extends TrackData {
   strings: string[];
   ids: Float64Array;
-  starts: Float64Array;
-  ends: Float64Array;
+  starts: BigInt64Array;
+  ends: BigInt64Array;
   cpu: Int8Array;
   state: Uint16Array;  // Index into |strings|.
 }
@@ -46,7 +48,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,23 +68,14 @@
       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);
-
     const query = `
       select
-        (ts + ${bucketNs / 2}) / ${bucketNs} * ${bucketNs} as tsq,
+        (ts + ${resolution / 2n}) / ${resolution} * ${resolution} as tsq,
         ts,
         state = 'S' as is_sleep,
         max(dur) as dur,
@@ -92,8 +85,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
     `;
@@ -107,8 +100,8 @@
       resolution,
       length: numRows,
       ids: new Float64Array(numRows),
-      starts: new Float64Array(numRows),
-      ends: new Float64Array(numRows),
+      starts: new BigInt64Array(numRows),
+      ends: new BigInt64Array(numRows),
       strings: [],
       state: new Uint16Array(numRows),
       cpu: new Int8Array(numRows),
@@ -127,22 +120,21 @@
       return idx;
     }
     const it = queryRes.iter({
-      'tsq': NUM,
-      'ts': NUM,
-      'dur': NUM,
+      'tsq': LONG,
+      'ts': LONG,
+      'dur': LONG,
       'cpu': NUM,
       'state': STR_NULL,
       'ioWait': NUM_NULL,
       'id': NUM,
     });
     for (let row = 0; it.valid(); it.next(), row++) {
-      const startNsQ = it.tsq;
-      const startNs = it.ts;
-      const durNs = it.dur;
-      const endNs = startNs + durNs;
-
-      let endNsQ = Math.floor((endNs + bucketNs / 2 - 1) / bucketNs) * bucketNs;
-      endNsQ = Math.max(endNsQ, startNsQ + bucketNs);
+      const startQ = it.tsq;
+      const start = it.ts;
+      const dur = it.dur;
+      const end = start + dur;
+      const minEnd = startQ + resolution;
+      const endQ = BIMath.max(BIMath.quant(end, resolution), minEnd);
 
       const cpu = it.cpu;
       const state = it.state || undefined;
@@ -151,10 +143,10 @@
 
       // We should never have the end timestamp being the same as the bucket
       // start.
-      assertFalse(startNsQ === endNsQ);
+      assertFalse(startQ === endQ);
 
-      data.starts[row] = fromNs(startNsQ);
-      data.ends[row] = fromNs(endNsQ);
+      data.starts[row] = startQ;
+      data.ends[row] = endQ;
       data.state[row] = internState(state, ioWait);
       data.ids[row] = id;
       data.cpu[row] = cpu;
@@ -186,7 +178,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 +195,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 +216,15 @@
       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 = HighPrecisionTimeSpan.fromTpTime(tStart, 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.tpTimeToPx(tStart);
+      const rectEnd = timeScale.tpTimeToPx(tEnd);
       const rectWidth = rectEnd - rectStart;
 
       const currentSelection = globals.state.currentSelection;
@@ -255,10 +252,9 @@
       if (isSelected) {
         drawRectOnSelected = () => {
           const rectStart =
-              Math.max(0 - EXCESS_WIDTH, timeScale.timeToPx(tStart));
+              Math.max(0 - EXCESS_WIDTH, timeScale.tpTimeToPx(tStart));
           const rectEnd = Math.min(
-              timeScale.timeToPx(visibleWindowTime.end) + EXCESS_WIDTH,
-              timeScale.timeToPx(tEnd));
+              windowSpan.end + EXCESS_WIDTH, timeScale.tpTimeToPx(tEnd));
           const color = colorForState(state);
           ctx.strokeStyle = `hsl(${color.h},${color.s}%,${color.l * 0.7}%)`;
           ctx.beginPath();
@@ -278,11 +274,10 @@
   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.toTPTime());
     if (index === -1) return false;
-
     const id = data.ids[index];
     globals.makeSelection(
         Actions.selectThreadState({id, trackId: this.trackState.id}));